mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-08 13:14:12 +08:00
🎨 Sync 2026-10-29 20:07:12
This commit is contained in:
1 parent
0546d1d8e4
commit
737acd5a18
182 files changed
+10470
-1649
No files matched your search
+63
-21
@@ -1,14 +1,21 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=isp-split
|
||||
PKG_VERSION:=0.2.2
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=0.3.0
|
||||
PKG_RELEASE:=4
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=jihebube <jihebube@users.noreply.github.com>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
# 线路体检(linecheck)自 0.3.0 起随包发货,默认 1。
|
||||
# 1 = 完整功能(默认,正式发行版走这条路径);
|
||||
# 0 = 构建期物理剥离:实现、CLI、conf 键、LuCI 页/菜单、socat 依赖全部不进包。
|
||||
# 小闪存 / 特殊渠道的可选剥离版,必须显式指定 FEATURE_LINECHECK=0。
|
||||
# 这是构建期开关,不是运行时开关:0 的产物里没有可被重新打开的代码。
|
||||
FEATURE_LINECHECK?=1
|
||||
|
||||
define Package/isp-split
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
@@ -19,7 +26,7 @@ define Package/isp-split
|
||||
# Kwrt 源里 nft 版的包名是 mwan3-nft,与其他源命名不一致。
|
||||
# nftables 版 mwan3 是硬性前提,由 install.sh 与 router-preflight 检查,
|
||||
# 并在 description 里说明。
|
||||
DEPENDS:=+nftables +curl +mwan3-nft
|
||||
DEPENDS:=+nftables +curl +mwan3-nft $(if $(filter 1,$(FEATURE_LINECHECK)),+socat)
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
@@ -34,6 +41,18 @@ define Package/isp-split/description
|
||||
iptables 版 mwan3 (2.12.5),或从 Release 下载
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
rm -rf $(PKG_BUILD_DIR)
|
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) ./files $(PKG_BUILD_DIR)/files
|
||||
# 只调用仓库里那一份 strip-linecheck.sh(随包内 strip-linecheck.sh 逐字节
|
||||
# 一致),不再手抄 sed:F7 的泄漏就是 SDK 侧另抄一份、覆盖不全造成的。
|
||||
# 脚本自己会做标记配对、零残留、CSS `.lc-*` 与 sh -n 断言。
|
||||
if [ "$(FEATURE_LINECHECK)" != "1" ]; then \
|
||||
sh $(CURDIR)/strip-linecheck.sh $(PKG_BUILD_DIR)/files; \
|
||||
fi
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
@@ -55,16 +74,25 @@ fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
# 卸载后的兜底清理:cron、命名计数器、dnsmasq confdir、数据目录。
|
||||
# 正常路径由 prerm 的 isp-split uninstall 完成,这里防止二进制先被删掉。
|
||||
# 卸载后的兜底清理。正常路径是 prerm 里的 `isp-split uninstall`(它调
|
||||
# /usr/lib/isp-split/teardown.sh,与 scripts/install.sh --uninstall 同一份实现)。
|
||||
# 但 opkg/apk 会先删包文件再跑 postrm,那时 teardown.sh 已经不在盘上,
|
||||
# 所以这里必须自带一份等价的内联清单 —— 这是"三条路径同源"的唯一例外,
|
||||
# 由 tools/selftest.sh 第 21 节做锚点双向核对:teardown.sh 的 runtime 锚点
|
||||
# 必须逐条出现在本块里,本块也不许出现清单外的清理动作。
|
||||
define Package/isp-split/postrm
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] && exit 0
|
||||
case "$${1:-}" in
|
||||
upgrade|pre-upgrade|*upgrade*) exit 0 ;;
|
||||
esac
|
||||
# 共享实现在盘上就用它(少数包管理器/强制删除会在删文件前跑 postrm)。
|
||||
if [ -f /usr/lib/isp-split/teardown.sh ]; then
|
||||
sh /usr/lib/isp-split/teardown.sh all
|
||||
exit $$?
|
||||
fi
|
||||
if [ -f /etc/crontabs/root ]; then
|
||||
# mktemp 而不是 /tmp/.ispcron.$$:PID 可预测,符号链接能抢占(SEC-1)
|
||||
# mktemp 而不是固定 /tmp 名:PID 可预测,符号链接能抢占(SEC-1)
|
||||
_cron_tmp=$$(mktemp /etc/.isp-split-cron.XXXXXX) || _cron_tmp=""
|
||||
if [ -n "$$_cron_tmp" ]; then
|
||||
grep -v 'isp-split' /etc/crontabs/root > "$$_cron_tmp" 2>/dev/null || true
|
||||
@@ -73,16 +101,20 @@ if [ -f /etc/crontabs/root ]; then
|
||||
/etc/init.d/cron restart >/dev/null 2>&1 || true
|
||||
fi
|
||||
nft delete counter inet mwan3 isp_fallback >/dev/null 2>&1 || true
|
||||
_state=/etc/isp-split/state/dnsmasq.confdir.orig
|
||||
# B-5:状态文件已改名 dnsmasq.confdir.migrated(记的是迁移后的清理值)。
|
||||
# 旧名 dnsmasq.confdir.orig 兼容读一次。
|
||||
_state=/etc/isp-split/state/dnsmasq.confdir.migrated
|
||||
[ -f "$$_state" ] || _state=/etc/isp-split/state/dnsmasq.confdir.orig
|
||||
if [ -f "$$_state" ]; then
|
||||
_orig=$$(cat "$$_state" 2>/dev/null)
|
||||
_orig=$$(head -1 "$$_state" 2>/dev/null)
|
||||
if [ -n "$$_orig" ]; then
|
||||
uci -q set "dhcp.@dnsmasq[0].confdir=$$_orig" 2>/dev/null || true
|
||||
else
|
||||
uci -q delete dhcp.@dnsmasq[0].confdir 2>/dev/null || true
|
||||
fi
|
||||
uci -q commit dhcp 2>/dev/null || true
|
||||
rm -f "$$_state"
|
||||
rm -f /etc/isp-split/state/dnsmasq.confdir.migrated \
|
||||
/etc/isp-split/state/dnsmasq.confdir.orig 2>/dev/null || true
|
||||
fi
|
||||
# BUG-6:OpenClash 接管时 dnsmasq 实际加载的是 /var/etc/dnsmasq.conf.*
|
||||
# 里 conf-dir= 指向的目录,必须按同一来源清理,不能只删 /tmp/dnsmasq.d。
|
||||
@@ -100,6 +132,9 @@ for _f in /var/etc/dnsmasq.conf.*; do
|
||||
done
|
||||
done
|
||||
rm -f /tmp/dnsmasq.d/isp-split.conf /etc/isp-split/dnsmasq.d/isp-split.conf 2>/dev/null || true
|
||||
# F-14:install.sh 留下的 /etc/isp-split.conf.bak.* 与主配置一并清掉(与 teardown.sh
|
||||
# 同策略:默认删,逐条打印由 teardown.sh 负责;这里只兜底)。
|
||||
rm -f /etc/isp-split.conf /etc/isp-split.conf.bak.* 2>/dev/null || true
|
||||
rm -rf /etc/isp-split 2>/dev/null || true
|
||||
exit 0
|
||||
endef
|
||||
@@ -112,19 +147,26 @@ define Package/isp-split/install
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_DIR) $(1)/etc/isp-split/data
|
||||
|
||||
$(INSTALL_BIN) ./files/usr/sbin/isp-split $(1)/usr/sbin/isp-split
|
||||
$(INSTALL_BIN) ./files/etc/init.d/isp-split $(1)/etc/init.d/isp-split
|
||||
$(INSTALL_BIN) ./files/usr/lib/isp-split/build.sh $(1)/usr/lib/isp-split/build.sh
|
||||
$(INSTALL_BIN) ./files/etc/uci-defaults/99-isp-split $(1)/etc/uci-defaults/99-isp-split
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/usr/sbin/isp-split $(1)/usr/sbin/isp-split
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/init.d/isp-split $(1)/etc/init.d/isp-split
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/build.sh $(1)/usr/lib/isp-split/build.sh
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/uci-defaults/99-isp-split $(1)/etc/uci-defaults/99-isp-split
|
||||
# F-14:卸载清理的唯一实现。prerm(isp-split uninstall)与
|
||||
# scripts/install.sh --uninstall 都调它;postrm 在二进制先被删掉时
|
||||
# 用内联等价清单兜底(锚点由 selftest 第 21 节双向核对)。
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/teardown.sh $(1)/usr/lib/isp-split/teardown.sh
|
||||
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/cidr2range.awk $(1)/usr/lib/isp-split/cidr2range.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/merge.awk $(1)/usr/lib/isp-split/merge.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/exclude.awk $(1)/usr/lib/isp-split/exclude.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/range2cidr.awk $(1)/usr/lib/isp-split/range2cidr.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/rules-splice.awk $(1)/usr/lib/isp-split/rules-splice.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/badcidr.awk $(1)/usr/lib/isp-split/badcidr.awk
|
||||
$(INSTALL_DATA) ./files/usr/lib/isp-split/ispdiff.awk $(1)/usr/lib/isp-split/ispdiff.awk
|
||||
$(INSTALL_CONF) ./files/etc/isp-split.conf $(1)/etc/isp-split.conf
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/cidr2range.awk $(1)/usr/lib/isp-split/cidr2range.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/merge.awk $(1)/usr/lib/isp-split/merge.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/exclude.awk $(1)/usr/lib/isp-split/exclude.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/range2cidr.awk $(1)/usr/lib/isp-split/range2cidr.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/rules-splice.awk $(1)/usr/lib/isp-split/rules-splice.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/badcidr.awk $(1)/usr/lib/isp-split/badcidr.awk
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/ispdiff.awk $(1)/usr/lib/isp-split/ispdiff.awk
|
||||
if [ -f $(PKG_BUILD_DIR)/files/usr/lib/isp-split/lc-run ]; then \
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/usr/lib/isp-split/lc-run $(1)/usr/lib/isp-split/lc-run; \
|
||||
fi
|
||||
$(INSTALL_CONF) $(PKG_BUILD_DIR)/files/etc/isp-split.conf $(1)/etc/isp-split.conf
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,isp-split))
|
||||
@@ -169,3 +169,84 @@ PROBE_URLS="
|
||||
https://mirrors.huaweicloud.com/ubuntu-releases/24.04/ubuntu-24.04.3-live-server-amd64.iso
|
||||
https://mirrors.aliyun.com/ubuntu-releases/24.04/ubuntu-24.04.3-live-server-amd64.iso
|
||||
"
|
||||
|
||||
# lc:begin
|
||||
# ---------------------------------------------------------------- 线路体检(0.3.0)
|
||||
#
|
||||
# `isp-split linecheck` 对每条 WAN 做:本机口 IP 分类(公网/CGNAT/私网)、
|
||||
# curl --interface 出口 IP、同运营商目标四段延迟、IP 归属地、双 STUN NAT 判定、
|
||||
# 评分与一句话建议。结果缓存 LINE_CHECK_MIN_INTERVAL 秒,--force 可绕过。
|
||||
#
|
||||
# 注意:NAT 只给「公网 / 锥形 / 对称或多出口 / CGNAT / 未知」五态,
|
||||
# 不做 NAT1-4 细分——公网 STUN 服务器基本不支持 CHANGE-REQUEST(见可行性报告)。
|
||||
# 体检不会修改 mwan3/nft 规则;失败只降级单项,不影响上网。
|
||||
|
||||
# 总开关:0=完全不执行(LuCI 页面也会提示已关闭)
|
||||
LINE_CHECK_ENABLE=1
|
||||
|
||||
# 归属地隐私开关:1=允许向第三方查询出口 IP 归属地;0=只关闭归属地查询
|
||||
#
|
||||
# 开启后,体检会把**每条 WAN 的出口公网 IP**发送给第三方(默认百度,
|
||||
# 失败时 ip-api.com)查询归属地。只发送出口 IP,不发送内网地址、设备名、
|
||||
# MAC、流量内容。ip-api 使用 HTTP,传输不加密;介意时请设为 0,或把
|
||||
# LINE_CHECK_GEO_PROVIDER 固定为 baidu。
|
||||
# 关闭后不发起任何归属地请求,卡片显示「归属地:未查询(隐私开关关闭)」。
|
||||
# 省/市不可知,但仍可用本地 @isp_* 集合判断运营商大类,不需要外发。
|
||||
# 注意:此开关只关归属地;出口 IP 回显、STUN 与延迟探测仍会外发。
|
||||
# 要完全停止体检,请设 LINE_CHECK_ENABLE=0。
|
||||
LINE_CHECK_GEO=1
|
||||
|
||||
# 归属地服务商:auto | baidu | ip-api | off
|
||||
# auto 先百度(HTTPS、国内可达),失败转 ip-api(HTTP)
|
||||
# baidu 只用百度;失败显示「归属地未知」,不会自动转 HTTP 源
|
||||
# ip-api 只用 ip-api(HTTP,明文)
|
||||
# off 等同 LINE_CHECK_GEO=0
|
||||
LINE_CHECK_GEO_PROVIDER="auto"
|
||||
|
||||
# 归属地缓存有效期(秒)。同一个出口 IP 在有效期内只查一次。
|
||||
LINE_CHECK_GEO_TTL=86400
|
||||
|
||||
# 手动触发的最小间隔(秒)。间隔内重复执行直接返回上次结果(缓存),
|
||||
# LuCI 的「立即体检」按钮也按这个值节流。范围 15-3600。
|
||||
LINE_CHECK_MIN_INTERVAL=60
|
||||
|
||||
# 单次探测量(1-10 秒):curl --connect-timeout 与 socat -T 都用它。
|
||||
# 宽带正常时 3 秒足够;线路很差可调到 5,但整次体检会更慢。
|
||||
LINE_CHECK_TIMEOUT=3
|
||||
|
||||
# 单条 WAN 的探测预算(秒)。超过后跳过剩余的 STUN/归属地,
|
||||
# 只保留已完成的项目并在明细里标注,避免 LuCI/rpcd 超时(建议 15-25)。
|
||||
LINE_CHECK_BUDGET=20
|
||||
|
||||
# 每条 WAN 的同运营商探测目标:<运营商源名>:<域名1>,<域名2>
|
||||
# 源名必须与 ISP_TABLE 第一列一致;域名运行时解析,再用对应 @isp_* 集合
|
||||
# 校验归属(不随机抽集合 IP、不扫端口)。全部失败时用下面的跨网基线兜底,
|
||||
# 并在卡片上标注「跨网基线」,延迟项最多 15 分。
|
||||
LINE_CHECK_TARGETS="
|
||||
chinatelecom:www.chinatelecom.com.cn,www.21cn.com
|
||||
unicom_cnc:www.10010.com,www.chinaunicom.com.cn
|
||||
cmcc:www.migu.cn,www.10086.cn
|
||||
cernet:www.tsinghua.edu.cn,www.pku.edu.cn
|
||||
"
|
||||
|
||||
# 跨网基线目标(仅用于判断“链路是否通”,不计入同网延迟分)
|
||||
LINE_CHECK_BASELINE="www.baidu.com,www.qq.com"
|
||||
|
||||
# STUN 服务器:<域名或 IP>:<端口>,按顺序解析,取两个不同 IP 各探一次。
|
||||
# 同一本地端口 + so-bindtodevice 锁线;失败自动换下一个/重试一次。
|
||||
# 公网 STUN 可达性会波动,不要只写一个;UDP 3478 被阻断时只降级为“未知”。
|
||||
# 可选:stun.chat.bilibili.com:3478(实测有时无 A 记录,按需加回列表)
|
||||
LINE_CHECK_STUN_SERVERS="
|
||||
stun.miwifi.com:3478
|
||||
stun.douyucdn.cn:18000
|
||||
stun.freeswitch.org:3478
|
||||
stun.cloudflare.com:3478
|
||||
"
|
||||
|
||||
# 出口 IP 回显服务(逗号分隔,按顺序尝试)。只用于拿公网出口 IP。
|
||||
LINE_CHECK_ECHO_URLS="https://api.ipify.org,https://ipv4.icanhazip.com"
|
||||
|
||||
# 体检请求的 User-Agent。部分运营商站点对默认 curl UA 返回 412/418,
|
||||
# 必须带浏览器 UA;不要改成空值。
|
||||
LINE_CHECK_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
||||
# lc:end
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/bin/sh
|
||||
# 数据管线:原始 CIDR -> 归一化 -> 按优先级互斥化 -> 每运营商一个 CIDR 文件
|
||||
#
|
||||
# 用法: build.sh <rawdir> <outdir> <优先级列表>
|
||||
# 用法: build.sh <rawdir> <outdir> <优先级列表> [<占位列表>]
|
||||
# rawdir : 含 <isp>.raw 的目录,每行一个 CIDR
|
||||
# 优先级 : 空格分隔的运营商名,排在前面的优先占有重叠地址
|
||||
# 占位列表: 可选,只有列在这里的运营商会把地址记入 claimed(差集占位)。
|
||||
# 缺省=全部优先级行都占位(保持旧行为)。cmd_fetch 传
|
||||
# enabled=1 的行:禁用行仍按原顺序生成自己的 .txt,但不抢地址。
|
||||
#
|
||||
# 设计要点:
|
||||
# 1. 每个集合内部先合并去重(RFC1918 无关,只做区间合并)
|
||||
@@ -15,6 +18,11 @@ set -eu
|
||||
RAW=$1
|
||||
OUT=$2
|
||||
PRIORITY=$3
|
||||
if [ $# -ge 4 ]; then
|
||||
CLAIM=$4
|
||||
else
|
||||
CLAIM=$PRIORITY
|
||||
fi
|
||||
|
||||
LIB=${ISP_SPLIT_LIB:-$(dirname "$0")}
|
||||
WORK=$OUT/.work
|
||||
@@ -42,11 +50,17 @@ for isp in $PRIORITY; do
|
||||
# 3. 区间 -> 最小 CIDR 集合
|
||||
awk -f "$LIB/range2cidr.awk" "$WORK/$isp.excl" > "$OUT/$isp.txt"
|
||||
|
||||
# 4. 已占用集合累加(需重新合并以保持有序且互不重叠)
|
||||
cat "$WORK/claimed" "$WORK/$isp.excl" |
|
||||
sort |
|
||||
awk -f "$LIB/merge.awk" > "$WORK/claimed.new"
|
||||
mv "$WORK/claimed.new" "$WORK/claimed"
|
||||
# 4. 已占用集合累加(需重新合并以保持有序且互不重叠)。
|
||||
# F-27:只有占位列表里的行才累加——禁用行不再抢低优先级启用集合
|
||||
# 的地址;但它的 .txt 仍按原优先级序号生成,方便重新启用/预演。
|
||||
case " $CLAIM " in
|
||||
*" $isp "*)
|
||||
cat "$WORK/claimed" "$WORK/$isp.excl" |
|
||||
sort |
|
||||
awk -f "$LIB/merge.awk" > "$WORK/claimed.new"
|
||||
mv "$WORK/claimed.new" "$WORK/claimed"
|
||||
;;
|
||||
esac
|
||||
|
||||
# 统计
|
||||
n_raw=$(awk -f "$LIB/cidr2range.awk" "$src" | wc -l | tr -d ' ')
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# 后台探测引擎入口(仅 --background 模式使用)。
|
||||
#
|
||||
# ash 的命令替换 / 管道会 fork 出继承父进程 argv 的子 shell。如果探测直接
|
||||
# 在持锁 worker 里跑,进程表会把那些子 shell 也数成额外 worker(fix29 实测
|
||||
# 1 个真 worker + 3 个子 shell = 4)。这里用独立 argv 的入口运行主脚本,
|
||||
# 进程表里带 "isp-split 子命令" 的只剩唯一那个持锁监督者。
|
||||
#
|
||||
# 参数原样透传:lc-run <子命令> <参数...>
|
||||
# 被加载的主脚本路径由监督者通过 ISP_SPLIT_LC_SELF 传入(argv 里不带路径,
|
||||
# 否则又会命中 "isp-split <子命令>" 的进程计数)。
|
||||
. "${ISP_SPLIT_LC_SELF:-/usr/sbin/isp-split}"
|
||||
@@ -0,0 +1,420 @@
|
||||
#!/bin/sh
|
||||
# 卸载清理的唯一实现(F-14)
|
||||
#
|
||||
# 背景:卸载有三条路径,以前各写各的清单:
|
||||
# 1. isp-split uninstall(cmd_uninstall)——摘规则、删策略、删数据目录,
|
||||
# 但不删命名计数器 isp_fallback,也不删 /usr/sbin/isp-split 等文件
|
||||
# 2. scripts/install.sh --uninstall——只删 LuCI 资源,脚本与配置全留着
|
||||
# 3. 包 postrm——删 cron/计数器/confdir,包文件则由包管理器删
|
||||
# 结果:Hermes 动态复现 install.sh --uninstall rc=0,而
|
||||
# /usr/sbin/isp-split、/etc/init.d/isp-split、/usr/lib/isp-split/、
|
||||
# /etc/isp-split.conf、isp_fallback 计数器、.conf.bak.* 全留着。
|
||||
#
|
||||
# 现在三条路径都走这里,清单只有一份:
|
||||
# isp-split uninstall -> 先摘 mwan3 规则(要 rules-splice.awk),再 all
|
||||
# scripts/install.sh --uninstall -> 先调 isp-split uninstall(若在),再 all
|
||||
# 包 postrm -> 二进制还在就调本脚本 runtime;包文件已被删时
|
||||
# 才走内联兜底(清单锚点由 selftest 第 21 节双向核对)
|
||||
#
|
||||
# 用法:
|
||||
# sh teardown.sh all # mwan3 段 + runtime 残留 + 文件
|
||||
# sh teardown.sh mwan3 # 只删 mwan3 里 isp_split=1 的 ipset/policy/member
|
||||
# sh teardown.sh runtime # cron 行 / isp_fallback 计数器 / dnsmasq confdir 与片段 / LuCI 缓存
|
||||
# sh teardown.sh files # 本工具安装的文件(含 /etc/isp-split 数据目录)
|
||||
# sh teardown.sh --list # 打印清单(selftest 与 postrm 漂移断言用),不动作
|
||||
# 选项:
|
||||
# --keep-conf-bak # 保留 /etc/isp-split.conf.bak.*(默认删除并逐条打印)
|
||||
#
|
||||
# 为什么默认删 .conf.bak.*:它们是安装脚本每次覆盖安装都会新生成的副本,
|
||||
# 不是用户手写的备份。留着等于"卸载"留下一批与工具同名同前缀的孤儿文件,
|
||||
# 重装时再叠一层;删除前会逐条打印路径,想留档可以照路径先 cp 走。要长期
|
||||
# 留档就显式 --keep-conf-bak,行为可选且可见。
|
||||
#
|
||||
# 环境变量(与 isp-split 同名同义;自检/打包用):
|
||||
# ISP_SPLIT_DESTDIR 根前缀(默认空)。设置后所有绝对路径都加上它,
|
||||
# 用于在沙箱里演练卸载,不碰宿主 /usr /etc
|
||||
# ISP_SPLIT_BASE /etc/isp-split
|
||||
# ISP_SPLIT_CONF /etc/isp-split.conf
|
||||
# ISP_SPLIT_CRONTAB /etc/crontabs/root
|
||||
# ISP_SPLIT_LIB /usr/lib/isp-split
|
||||
# DNSDIR 旧版片段目录(默认 <BASE>/dnsmasq.d)
|
||||
# DNSMASQ_DEFAULT_DIR dnsmasq 默认 conf-dir(默认 /tmp/dnsmasq.d)
|
||||
# DNSMASQ_CONF_GLOB 运行配置 glob(默认 /var/etc/dnsmasq.conf.*)
|
||||
# MWAN3_SERVICE / CRON_SERVICE / RPCD_SERVICE 服务脚本(自检可换 stub)
|
||||
set -u
|
||||
|
||||
DESTDIR=${ISP_SPLIT_DESTDIR:-}
|
||||
case "$DESTDIR" in
|
||||
"" | "/") DESTDIR="" ;;
|
||||
/*) DESTDIR=${DESTDIR%/} ;;
|
||||
*)
|
||||
echo "ISP_SPLIT_DESTDIR 必须是绝对路径: '$DESTDIR'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
BASE=${ISP_SPLIT_BASE:-$DESTDIR/etc/isp-split}
|
||||
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}
|
||||
DNSDIR=${DNSDIR:-$BASE/dnsmasq.d}
|
||||
DNSMASQ_DEFAULT_DIR=${DNSMASQ_DEFAULT_DIR:-$DESTDIR/tmp/dnsmasq.d}
|
||||
DNSMASQ_CONF_GLOB=${DNSMASQ_CONF_GLOB:-$DESTDIR/var/etc/dnsmasq.conf.*}
|
||||
STATE_DIR=$BASE/state
|
||||
# B-5:状态文件名与语义对齐——它记的是"迁移后写进 UCI 的清理值",不是原值。
|
||||
STATE_NEW=$STATE_DIR/dnsmasq.confdir.migrated
|
||||
STATE_OLD=$STATE_DIR/dnsmasq.confdir.orig
|
||||
MWAN3_SERVICE=${MWAN3_SERVICE:-$DESTDIR/etc/init.d/mwan3}
|
||||
CRON_SERVICE=${CRON_SERVICE:-$DESTDIR/etc/init.d/cron}
|
||||
RPCD_SERVICE=${RPCD_SERVICE:-$DESTDIR/etc/init.d/rpcd}
|
||||
|
||||
KEEP_CONF_BAK=0
|
||||
SCOPE=""
|
||||
|
||||
say() { printf '[teardown] %s\n' "$*"; }
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# 只有真实根(DESTDIR 为空)才动服务:沙箱演练里重启服务既没意义,也会误伤宿主。
|
||||
svc_restart() {
|
||||
[ -z "$DESTDIR" ] || return 0
|
||||
[ -x "$1" ] || return 0
|
||||
"$1" restart >/dev/null 2>&1 || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- 清单
|
||||
|
||||
# 文件/目录清单(相对 DESTDIR)。--list 与 files 段共用,杜绝两处漂移。
|
||||
files_list() {
|
||||
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
|
||||
path etc/uci-defaults/99-isp-split
|
||||
dir www/luci-static/resources/view/isp-split
|
||||
dir www/luci-static/resources/isp-split
|
||||
path usr/share/luci/menu.d/luci-app-isp-split.json
|
||||
# lc:begin
|
||||
# 体检页的 menu 节点只在完整版里存在;0 版(FEATURE_LINECHECK=0)连这一行
|
||||
# 都不该留在卸载清单里(strip-linecheck.sh 的零残留断言会抓,19b.2 覆盖)。
|
||||
path usr/share/luci/menu.d/luci-app-isp-split-linecheck.json
|
||||
# lc:end
|
||||
path usr/share/rpcd/acl.d/luci-app-isp-split.json
|
||||
EOF
|
||||
}
|
||||
|
||||
# runtime 的锚点:每一条都必须能在 Makefile postrm 的内联兜底里找到
|
||||
# (selftest 第 21 节双向核对,防"三条路径又各写一份")。
|
||||
runtime_anchors() {
|
||||
cat <<'EOF'
|
||||
anchor nft delete counter inet mwan3 isp_fallback
|
||||
anchor /etc/isp-split/state/dnsmasq.confdir.migrated
|
||||
anchor /etc/isp-split/state/dnsmasq.confdir.orig
|
||||
anchor conf-dir=
|
||||
anchor /tmp/dnsmasq.d/isp-split.conf
|
||||
anchor /etc/isp-split/dnsmasq.d/isp-split.conf
|
||||
anchor .isp-split-cron.XXXXXX
|
||||
anchor rm -rf /etc/isp-split
|
||||
EOF
|
||||
}
|
||||
|
||||
print_list() {
|
||||
echo "== 卸载清单(唯一实现:teardown.sh)=="
|
||||
echo "-- mwan3(UCI 段 + 服务重载)--"
|
||||
echo " 带 isp_split=1 标记的 ipset 段(含改名/删除的孤儿段)"
|
||||
echo " 带 isp_split=1 标记的 policy 段"
|
||||
echo " 带 isp_split=1 标记的 member 段(仍被其它策略引用的保留)"
|
||||
echo " nft 规则块由 isp-split uninstall 的 rules-splice 摘除"
|
||||
echo "-- runtime 残留 --"
|
||||
echo " crontab 里的 isp-split 行"
|
||||
echo " nft 命名计数器 isp_fallback"
|
||||
echo " dnsmasq confdir 状态文件(恢复 UCI 后删除)"
|
||||
echo " dnsmasq 片段 isp-split.conf(探测目录 + 默认目录 + 旧版目录)"
|
||||
echo " LuCI 缓存目录"
|
||||
echo "-- 文件 --"
|
||||
files_list | while read -r _k _v; do
|
||||
case "$_k" in
|
||||
path) echo " $_v" ;;
|
||||
dir) echo " $_v/(目录)" ;;
|
||||
glob) echo " $_v(历史备份,默认删除;--keep-conf-bak 保留)" ;;
|
||||
esac
|
||||
done
|
||||
echo "-- 机器可读 --"
|
||||
files_list
|
||||
runtime_anchors
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- mwan3
|
||||
|
||||
# 按 isp_split=1 标记删,而不是按"当前 ISP_TABLE 的名字"删:改名/删除的
|
||||
# 旧段会漏掉,永远清不掉(B-4)。无标记的段一律不碰。
|
||||
cmd_mwan3() {
|
||||
if ! have uci; then
|
||||
say "没有 uci,跳过 mwan3 段清理"
|
||||
return 0
|
||||
fi
|
||||
_mw_n=0
|
||||
for _mw_sec in $(uci -q show mwan3 2>/dev/null |
|
||||
sed -n 's/^mwan3\.\([^=]*\)=ipset$/\1/p'); do
|
||||
[ "$(uci -q get "mwan3.$_mw_sec.isp_split" 2>/dev/null)" = "1" ] || continue
|
||||
uci -q delete "mwan3.$_mw_sec" 2>/dev/null && _mw_n=$((_mw_n + 1))
|
||||
done
|
||||
|
||||
_mw_np=0
|
||||
for _mw_sec in $(uci -q show mwan3 2>/dev/null |
|
||||
sed -n 's/^mwan3\.\([^=]*\)=policy$/\1/p'); do
|
||||
[ "$(uci -q get "mwan3.$_mw_sec.isp_split" 2>/dev/null)" = "1" ] || continue
|
||||
uci -q delete "mwan3.$_mw_sec" 2>/dev/null && _mw_np=$((_mw_np + 1))
|
||||
done
|
||||
|
||||
# member 段:仍被任何策略引用的不能删(删了 mwan3 会渲染失败)
|
||||
_mw_refs=" "
|
||||
for _mw_psec in $(uci -q show mwan3 2>/dev/null |
|
||||
sed -n 's/^mwan3\.\([^=]*\)=policy$/\1/p'); do
|
||||
for _mw_x in $(uci -q get "mwan3.$_mw_psec.use_member" 2>/dev/null); do
|
||||
_mw_refs="$_mw_refs$_mw_x "
|
||||
done
|
||||
done
|
||||
_mw_nm=0
|
||||
for _mw_sec in $(uci -q show mwan3 2>/dev/null |
|
||||
sed -n 's/^mwan3\.\([^=]*\)=member$/\1/p'); do
|
||||
[ "$(uci -q get "mwan3.$_mw_sec.isp_split" 2>/dev/null)" = "1" ] || continue
|
||||
case "$_mw_refs" in
|
||||
*" $_mw_sec "*)
|
||||
say "警告:成员段 $_mw_sec 仍被其它策略引用,保留"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
uci -q delete "mwan3.$_mw_sec" 2>/dev/null && _mw_nm=$((_mw_nm + 1))
|
||||
done
|
||||
uci -q commit mwan3 2>/dev/null || true
|
||||
|
||||
[ "$_mw_n" -gt 0 ] &&
|
||||
say "已删除 $_mw_n 个带 isp_split 标记的 ipset 段(含改名/删除的孤儿段)"
|
||||
[ "$_mw_np" -gt 0 ] || [ "$_mw_nm" -gt 0 ] &&
|
||||
say "已删除 $_mw_np 个带 isp_split 标记的策略段、$_mw_nm 个成员段"
|
||||
|
||||
# 规则已由 rules-splice 摘掉,这里 reload 让集合/策略随之消失。
|
||||
if [ -x "$MWAN3_SERVICE" ]; then
|
||||
"$MWAN3_SERVICE" reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- runtime
|
||||
|
||||
# B-5:旧名里存的是"迁移后的清理值",名字与语义不符(PROPOSAL-0.2.2 §B-5)。
|
||||
# 升级上来的设备可能还留着旧名:迁到新名,之后只认新名。迁移失败也不阻断,
|
||||
# 因为读取侧两个名字都认。
|
||||
_state_adopt() {
|
||||
[ -f "$STATE_NEW" ] && return 0
|
||||
[ -f "$STATE_OLD" ] || return 0
|
||||
if mv -f "$STATE_OLD" "$STATE_NEW" 2>/dev/null; then
|
||||
say "旧状态文件 dnsmasq.confdir.orig 已迁移为 dnsmasq.confdir.migrated(B-5)"
|
||||
else
|
||||
cp -f "$STATE_OLD" "$STATE_NEW" 2>/dev/null && rm -f "$STATE_OLD"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_state_read() {
|
||||
if [ -f "$STATE_NEW" ]; then
|
||||
head -1 "$STATE_NEW" 2>/dev/null | tr -d '\r'
|
||||
elif [ -f "$STATE_OLD" ]; then
|
||||
head -1 "$STATE_OLD" 2>/dev/null | tr -d '\r'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# 片段可能残留的目录:运行配置里的 conf-dir=(权威源)+ UCI 当前值 +
|
||||
# 状态文件记录值 + 默认目录 + 旧版目录,去重后逐行输出。
|
||||
_frag_dirs() {
|
||||
{
|
||||
for _fd_f in $DNSMASQ_CONF_GLOB; do
|
||||
[ -f "$_fd_f" ] || continue
|
||||
sed -n 's/^[[:space:]]*conf-dir=//p' "$_fd_f" 2>/dev/null
|
||||
done
|
||||
printf '%s\n' "${_frag_uci:-}" "${_frag_state:-}" \
|
||||
"$DNSMASQ_DEFAULT_DIR" "$DNSDIR"
|
||||
} | tr ' ' '\n' |
|
||||
sed -e 's/,.*$//' -e 's/^"//' -e 's/"$//' -e 's:/*$::' |
|
||||
awk 'NF && !seen[$0]++'
|
||||
}
|
||||
|
||||
cmd_runtime() {
|
||||
_state_adopt
|
||||
_orig=$(_state_read)
|
||||
_frag_state=$_orig
|
||||
_frag_uci=""
|
||||
have uci && _frag_uci=$(uci -q get dhcp.@dnsmasq[0].confdir 2>/dev/null || true)
|
||||
|
||||
# 1) 恢复 UCI confdir:只有真的改过(有状态文件)才碰 UCI。
|
||||
if [ -f "$STATE_NEW" ] || [ -f "$STATE_OLD" ]; then
|
||||
if [ -n "$_orig" ]; then
|
||||
if [ "$_frag_uci" != "$_orig" ]; then
|
||||
if uci -q set "dhcp.@dnsmasq[0].confdir=$_orig" 2>/dev/null &&
|
||||
uci -q commit dhcp 2>/dev/null; then
|
||||
say "已恢复 dnsmasq confdir=$_orig"
|
||||
fi
|
||||
fi
|
||||
elif [ -n "$_frag_uci" ]; then
|
||||
if uci -q delete dhcp.@dnsmasq[0].confdir 2>/dev/null &&
|
||||
uci -q commit dhcp 2>/dev/null; then
|
||||
say "已恢复 dnsmasq confdir(回落到默认 $DNSMASQ_DEFAULT_DIR)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
rm -f "$STATE_NEW" "$STATE_OLD" 2>/dev/null || true
|
||||
|
||||
# 2) 清片段。探测来源优先,但不依赖探测是否成功——目录列表是并集。
|
||||
_n=0
|
||||
for _d in $(_frag_dirs); do
|
||||
[ -f "$_d/isp-split.conf" ] || continue
|
||||
rm -f "$_d/isp-split.conf" 2>/dev/null && _n=$((_n + 1))
|
||||
done
|
||||
[ "$_n" -gt 0 ] && say "已清理 $_n 处 dnsmasq 片段 isp-split.conf"
|
||||
|
||||
# 3) 命名计数器(旧版只有包 postrm 删它,脚本卸载路径漏了)
|
||||
if have nft && nft list table inet mwan3 >/dev/null 2>&1; then
|
||||
if nft list counter inet mwan3 isp_fallback >/dev/null 2>&1; then
|
||||
nft delete counter inet mwan3 isp_fallback >/dev/null 2>&1 &&
|
||||
say "已删除 nft 计数器 inet mwan3 isp_fallback"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) crontab 行。mktemp 而不是固定 /tmp 名:/tmp 世界可写,符号链接能抢占(SEC-1)。
|
||||
if [ -f "$CRONTAB" ] && grep -q 'isp-split' "$CRONTAB" 2>/dev/null; then
|
||||
_cron_dir=$(dirname "$CRONTAB")
|
||||
_cron_tmp=$(mktemp "$_cron_dir/.isp-split-cron.XXXXXX" 2>/dev/null) ||
|
||||
_cron_tmp=$(mktemp "$CRONTAB.XXXXXX" 2>/dev/null) || _cron_tmp=""
|
||||
if [ -n "$_cron_tmp" ]; then
|
||||
grep -v 'isp-split' "$CRONTAB" > "$_cron_tmp" 2>/dev/null || true
|
||||
if mv -f "$_cron_tmp" "$CRONTAB"; then
|
||||
say "已移除 crontab 里的 isp-split 行"
|
||||
svc_restart "$CRON_SERVICE"
|
||||
else
|
||||
rm -f "$_cron_tmp"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5) LuCI 缓存(页面文件在 files 段删)
|
||||
rm -rf "$DESTDIR"/tmp/luci-indexcache* "$DESTDIR"/tmp/luci-modulecache* 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- files
|
||||
|
||||
cmd_files() {
|
||||
_del=0
|
||||
_err=0
|
||||
while read -r _k _p; do
|
||||
case "$_k" in
|
||||
path)
|
||||
[ -e "$DESTDIR/$_p" ] || [ -L "$DESTDIR/$_p" ] || continue
|
||||
if rm -f "$DESTDIR/$_p"; then
|
||||
_del=$((_del + 1))
|
||||
else
|
||||
say "错误:删不掉 $DESTDIR/$_p"
|
||||
_err=1
|
||||
fi
|
||||
;;
|
||||
dir)
|
||||
[ -d "$DESTDIR/$_p" ] || continue
|
||||
if rm -rf "$DESTDIR/$_p"; then
|
||||
_del=$((_del + 1))
|
||||
else
|
||||
say "错误:删不掉 $DESTDIR/$_p"
|
||||
_err=1
|
||||
fi
|
||||
;;
|
||||
glob)
|
||||
# 这里必须真的做通配:把目录与文件名分开,目录加引号(容忍空格),
|
||||
# 文件名部分不加引号交给 shell 展开。整段引号会把 `*` 当成字面量,
|
||||
# 结果一个 .bak 都删不掉(自检 21.2/21.4 抓到的第一版 bug)。
|
||||
_gdir=$(dirname "$DESTDIR/$_p")
|
||||
_gbase=$(basename "$_p")
|
||||
for _g in "$_gdir"/$_gbase; do
|
||||
[ -f "$_g" ] || continue
|
||||
if [ "$KEEP_CONF_BAK" = "1" ]; then
|
||||
say "保留历史配置备份: $_g(--keep-conf-bak)"
|
||||
continue
|
||||
fi
|
||||
# 删除前逐条打印:想留档可以在同一台机器上先照路径 cp 走。
|
||||
say "删除历史配置备份: $_g"
|
||||
if rm -f "$_g"; then
|
||||
_del=$((_del + 1))
|
||||
else
|
||||
say "错误:删不掉 $_g"
|
||||
_err=1
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(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
|
||||
|
||||
if [ "$_del" -gt 0 ]; then
|
||||
say "已删除 $_del 项文件/目录"
|
||||
else
|
||||
say "没有需要删除的文件"
|
||||
fi
|
||||
if [ "$_err" -ne 0 ]; then
|
||||
say "有文件删不掉(只读挂载或权限问题),卸载不完整"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- main
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--keep-conf-bak) KEEP_CONF_BAK=1 ;;
|
||||
--list) SCOPE=list ;;
|
||||
all | mwan3 | runtime | files) SCOPE=$1 ;;
|
||||
-h | --help)
|
||||
sed -n '2,38p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1(可用:all|mwan3|runtime|files|--list|--keep-conf-bak)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$SCOPE" ]; then
|
||||
echo "用法: sh teardown.sh all|mwan3|runtime|files|--list [--keep-conf-bak]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case "$SCOPE" in
|
||||
list) print_list ;;
|
||||
mwan3) cmd_mwan3 ;;
|
||||
runtime) cmd_runtime ;;
|
||||
files) cmd_files ;;
|
||||
all)
|
||||
cmd_mwan3
|
||||
cmd_runtime
|
||||
svc_restart "$RPCD_SERVICE"
|
||||
cmd_files
|
||||
;;
|
||||
esac
|
||||
+3714
-184
File diff suppressed because it is too large.
Load diff
Executable
+125
@@ -0,0 +1,125 @@
|
||||
#!/bin/sh
|
||||
# 构建期剥离线路体检(FEATURE_LINECHECK=0)。
|
||||
#
|
||||
# 用法:sh strip-linecheck.sh <staging-root>
|
||||
#
|
||||
# 这是**唯一**的剥离实现。手工打包(scripts/build-ipk.sh)与 SDK/buildroot
|
||||
# (主包 / LuCI 包两份 Makefile 的 Build/Prepare)都必须调用它,不许再手抄
|
||||
# sed。F7 的根因就是 SDK 侧另抄了三行 sed:只覆盖 config.js,漏了 htdocs 的
|
||||
# isp-split.css,于是本地(走本脚本,全树扫)、VM 全绿,24.10 SDK 产物泄漏。
|
||||
#
|
||||
# 源文件里用成对标记包住 feature 专属内容(shell/conf/js/css 用 `lc:begin` /
|
||||
# `lc:end` 注释,Markdown 用 `<!-- lc:begin -->` / `<!-- lc:end -->`)。
|
||||
# 标记必须包住内容本体,不能只包一行标题注释:CSS 曾经只包了标题,`.lc-*`
|
||||
# 样式体裸奔进包,而“grep linecheck 字符串”的断言看不见它——所以本脚本除了
|
||||
# 字符串零残留,还断言标记配对、剥离后标记清零、CSS 里不留 `.lc-*` 样式类。
|
||||
#
|
||||
# 这不是运行时开关:删掉的内容不会进入包、不会进入安装目录,也不存在
|
||||
# 一个环境变量或 UCI 键能把它打开。
|
||||
set -eu
|
||||
|
||||
ROOT=${1:-}
|
||||
[ -n "$ROOT" ] || { echo "用法: sh strip-linecheck.sh <staging-root>" >&2; exit 1; }
|
||||
[ -d "$ROOT" ] || { echo "目录不存在: $ROOT" >&2; exit 1; }
|
||||
|
||||
WORK=$(mktemp -d 2>/dev/null) || WORK=$(mktemp -d /tmp/lcstrip.XXXXXX) ||
|
||||
{ echo "无法创建临时目录" >&2; exit 1; }
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
# 0) 标记配对自检:缺 lc:end 时 sed 会静默删到文件末尾,必须在剥离前拦住。
|
||||
cat > "$WORK/check-marks.sh" <<'EOS'
|
||||
#!/bin/sh
|
||||
rc=0
|
||||
for f do
|
||||
grep -Iq 'lc:begin\|lc:end' "$f" 2>/dev/null || continue
|
||||
awk '
|
||||
FNR == 1 { d = 0 }
|
||||
/lc:begin/ { d++ }
|
||||
/lc:end/ {
|
||||
d--
|
||||
if (d < 0) {
|
||||
printf "lc:end 出现在 lc:begin 之前: %s\n", FILENAME > "/dev/stderr"
|
||||
bad = 1
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!bad && d != 0) {
|
||||
printf "lc:begin 缺 lc:end(%d 层未闭合): %s\n", d, FILENAME > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$f" || { echo "标记不配对: $f" >&2; rc=1; }
|
||||
done
|
||||
exit $rc
|
||||
EOS
|
||||
find "$ROOT" -type f -exec sh "$WORK/check-marks.sh" {} \; || exit 1
|
||||
|
||||
del_blocks() {
|
||||
_f=$1
|
||||
grep -Iq 'lc:begin' "$_f" 2>/dev/null || return 0
|
||||
_tmp=$_f.lcstrip.$$
|
||||
sed '/lc:begin/,/lc:end/d' "$_f" > "$_tmp" || { rm -f "$_tmp"; return 1; }
|
||||
# 保留原权限位(isp-split 必须仍可执行)
|
||||
chmod --reference="$_f" "$_tmp" 2>/dev/null || chmod 644 "$_tmp"
|
||||
mv -f "$_tmp" "$_f"
|
||||
}
|
||||
|
||||
# 1) 文本文件里的标记区
|
||||
find "$ROOT" -type f | while IFS= read -r f; do
|
||||
del_blocks "$f" || touch "$WORK/del-failed"
|
||||
done
|
||||
[ ! -e "$WORK/del-failed" ] || { echo "剥离时有文件写入失败" >&2; exit 1; }
|
||||
|
||||
# 2) feature=1 专属文件
|
||||
find "$ROOT" -type f \
|
||||
\( -name 'linecheck.js' -o -name 'lc-run' -o -name 'luci-app-isp-split-linecheck.json' \) \
|
||||
-exec rm -f {} +
|
||||
|
||||
# 3) 空目录顺手收掉(LuCI view 目录只有 linecheck.js 时)
|
||||
find "$ROOT" -type d -empty -delete 2>/dev/null || true
|
||||
|
||||
# 4) 零残留断言(白名单为空)
|
||||
_leak=$(grep -rIl 'linecheck\|LINE_CHECK\|线路体检' "$ROOT" 2>/dev/null || true)
|
||||
if [ -n "$_leak" ]; then
|
||||
echo "剥离失败,以下文件仍有 linecheck 残留:" >&2
|
||||
printf '%s\n' "$_leak" | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4b) 标记必须被吃干净(配对自检通过 + sed 正常时必然成立;写死断言防回归)
|
||||
_marks=$(grep -rIl 'lc:begin\|lc:end' "$ROOT" 2>/dev/null || true)
|
||||
if [ -n "$_marks" ]; then
|
||||
echo "剥离失败,以下文件仍残留 lc:begin / lc:end 标记:" >&2
|
||||
printf '%s\n' "$_marks" | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4c) CSS:标记必须包住样式体。`.lc-*` 不含 linecheck 字样,字符串断言抓不到,
|
||||
# 所以单独断言;顺手断言花括号平衡,防止把规则体切成半截。
|
||||
find "$ROOT" -type f -name '*.css' | while IFS= read -r _css; do
|
||||
if grep -q '\.lc-' "$_css"; then
|
||||
echo "CSS 仍有 .lc-* 样式(标记没有包住样式体): $_css" >&2
|
||||
touch "$WORK/fail"
|
||||
fi
|
||||
# 用 tr 数花括号:老 busybox awk 对 gsub(/{/) 会报 "bad regex"
|
||||
_o=$(tr -cd '{' <"$_css" | wc -c | tr -d ' \n')
|
||||
_c=$(tr -cd '}' <"$_css" | wc -c | tr -d ' \n')
|
||||
if [ "$_o" != "$_c" ]; then
|
||||
echo "CSS 花括号不平衡(剥离切坏了规则体): $_css({=$_o }=$_c)" >&2
|
||||
touch "$WORK/fail"
|
||||
fi
|
||||
done
|
||||
[ ! -e "$WORK/fail" ] || exit 1
|
||||
|
||||
# 5) 语法自检(脚本仍能被 shell 解析)
|
||||
_main=$ROOT/usr/sbin/isp-split
|
||||
if [ -f "$_main" ]; then
|
||||
sh -n "$_main" || { echo "剥离后 isp-split 语法错误" >&2; exit 1; }
|
||||
fi
|
||||
_conf=$ROOT/etc/isp-split.conf
|
||||
if [ -f "$_conf" ]; then
|
||||
sh -n "$_conf" || { echo "剥离后 isp-split.conf 语法错误" >&2; exit 1; }
|
||||
fi
|
||||
|
||||
echo "已剥离线路体检: $ROOT"
|
||||
Reference in new issue
Block a user