Compare commits

..

7 Commits

42 changed files with 300 additions and 1870 deletions

View File

@@ -1,44 +0,0 @@
name: Daily Trends Factory
on:
schedule:
# 每天 UTC 18:00 运行 (北京时间凌晨 02:00)
- cron: '0 18 * * *'
workflow_dispatch:
permissions:
contents: write
jobs:
update-trends:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Execute Trends Engine
run: python scripts/fetch_trends.py
- name: Commit and Push
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add data/keywords/
# 防御机制:如果没有新数据,就静默退出,不产生空提交
if git diff --staged --quiet; then
echo "No changes, skipping."
exit 0
fi
# 策略:放弃危险的 amend 强制覆盖,采用带日期的标准安全提交
git commit -m "chore(data): 🤖 自动机兵:刷新全战区热点词库 [$(date +'%Y-%m-%d')]"
git push origin main

View File

@@ -10,19 +10,33 @@
专为解决 VPS IP 被 Google 等数据库错误定位到中国大陆/香港俗称“送中”等问题而生。IP-Sentinel 已从单机脚本全面跃升为 **Master-Agent 分布式架构**。它像影子一样潜伏在全球各地的服务器后台,通过高度拟真的真实用户行为为你默默积累 IP 权重,并允许你通过 Telegram 随时随地对整个舰队进行毫秒级“点名”与“遥控”。
## ✨ 核心极客特性 (Core Architecture)
## ✨ 核心极客特性
- 🎛 **L5 降维中枢与动态状态机 (Microservices Console)**:引入四级战区降维解析与双轨身份制。全 Inline Keyboard 原位重绘交互,支持在 TG 面板一键下发毫秒级模块热启停 (Toggle)、丝滑改名与日志抓取,底层数据库实时刷新,彻底告别刷屏烦恼
- 🔄 **SSOT 溯源与热更新装甲 (Smooth Upgrade Engine)**:全系脚本彻底消灭硬编码,部署时动态抓取云端版本信标。搭载状态机嗅探逻辑与 OTA 预警探针,一键回车瞬间完成配置继承、数据同步与无损换代。
- 🗺️ **全球拓扑矩阵与活体词库 (Global Nexus)**:守护版图横跨亚欧美三大洲。接入 GitHub Actions 云端流水线,每日静默同步全球各大区当日 Google 真实热搜榜单与高权重本土站点,让伪装行为永远贴合当地网络脉搏。
- 👻 **资产持久化与错峰调度 (Hash-Seeded Persona)**:摒弃随机抽取指纹,基于节点物理 IP 哈希永久锁定 3 个绝对专属设备,完美构建高权重真实家庭内网画像。叠加按需智能分频与随机防并发休眠,化解“惊群效应”。
- 🖧 **底层路由死锁与高精度探针 (Hard-Bind Routing)**:底层探测引擎强力接管 curl 核心参数 (`--interface`),将发出的每一滴伪装流量死死绑定在物理网卡或隧道 IP 上。配合多级 ISP 容灾链路,彻底杜绝双栈环境下的流量溢出与 API 误判。
- 🗺 **全球拓扑矩阵 (Global Nexus)**v3.1 跨洲际跃升。守护版图现已横跨亚、欧、美三大洲(美、日、英、德、法、新、港)。为每个国家注入极其硬核的“原生本地化”搜索词库与本土高权重站点(如政府、权威媒体、高铁网),真正实现“拟真融入”
**—— 💎 骨干基建特征 ——**
- 🏭 **自动化指纹兵工厂**:依托 GitHub Actions CI/CD 流水线,每月 1 日无人值守锻造 4000+ 带绝对物理分区的真实终端设备数据。
- 🔒 **叹息之墙 (Zero-Trust HMAC)**:底层通讯引入 时间戳 + HMAC-SHA256 军用级动态签名。指令有效期仅 60 秒(阅后即焚),未授权请求直接触发系统级 403 物理熔断,彻底免疫中间人抓包与重放攻击
- ☁️ **云端中枢 (Public Master)**:官方公共机器人 @OmniBeacon_bot,新手免自建,一键接入极速入伍!同时支持硬核极客私有化 SQLite 分布式部署。
- 👁️‍🗨️ **玻璃房透明遥测 (Glasshouse)**:基于 Cloudflare Workers 的全透明计数中枢,绝对零隐私收集,仅作原子累加,底层网关源码全开源
- 👻 **设备资产持久化 (Hash-Seeded Persona)**v3.2 核心换代。彻底摒弃传统的“随机抽取指纹”,引入基于节点物理 IP 的哈希锚定引擎。利用不可变哈希种子,为您的每台 VPS 在千万级指纹库中永久锁定 3 个绝对专属设备(如固定表现为 1台 Mac、1台 iPhone、1台 PC 交替上网)。完美构建高权重真实家庭内网画像,根除“僵尸网络”同质化特征!
- 🏭 **自动化指纹兵工厂 (Automated UA Factory)**:依托 GitHub Actions CI/CD 流水线,每月 1 日无人值守全自动生成 4000+ 带绝对物理分区的真实终端设备数据。配合边缘节点的守护进程静默拉取,实现千万级指纹资产的“自动驾驶”级演进
- 🖧 **底层路由死锁 (Hard-Bind Routing)**v3.2.1 热修复升级。底层探测引擎强力接管 curl 核心参数 (--interface),强制将发出的每一滴伪装流量死死绑定在您设定的物理网卡或隧道 IP 上,彻底杜绝双栈或多网卡环境下的流量溢出漏洞
- 🎯 **多级容灾与高精度探针 (High-Precision Probe)**v3.2.2 底层重构。重写战报模块与底层协议自适应逻辑,植入多级 ISP 容灾探针链路,并按“底层数据共识原则”智能清洗冗余 AS 号。确保在纯 V6、隧道或弱网环境下数据获取依然 100% 精准畅通。
- 🔄 **平滑热更新装甲 (Smooth Upgrade Engine)**v3.2.2 体验进化。全系植入状态机嗅探逻辑。无论是 Master 司令部还是 Agent 边缘节点再次执行部署脚本时将自动识别并继承历史配置、SQLite 数据库与锚定 IP一键回车即可瞬间完成无损换代告别繁琐的重复配置。
- ☁️ **云端中枢 (Public Master)**:引入官方公共机器人 @OmniBeacon_bot,新手无需部署 Master 司令部,部署 Agent 时一键回车即可调用官方加密网关30 秒极速入伍!
- 🧠 **分布式中枢 (Master-Agent)**:对于硬核极客,支持私有化部署。一台 Master 主控集成 SQLite 数据库,统管无数台 Agent 边缘节点,确保数据绝对私有。
- 🔒 **叹息之墙 (Zero-Trust HMAC)**:全面废弃明文 Token底层通讯引入 时间戳 + HMAC-SHA256 军用级动态签名。指令有效期仅 60 秒(阅后即焚),彻底免疫中间人抓包、重放攻击与端口爆破。
- 🛡️ **工业级并发与自净引擎**:底层 Webhook 采用多线程模型彻底免疫慢速耗尽攻击;独创“智能清道夫”逻辑,覆盖安装/升级时自动绞杀僵尸进程与冗余定时任务,绝对纯净,告别玄学冲突。
- 🎮 **TG 战术面板 (Command Center)**:无需记忆繁琐命令,全 Inline Keyboard 交互。支持一键下发伪装指令、一键索要精准战报、毫秒级抓取边缘节点实时运行日志。
- 👁️‍🗨️ **玻璃房透明遥测 (Glasshouse Telemetry)**:引入基于 Cloudflare Workers 的全透明计数中枢,首页动态徽章实时展示全球真实装机与调用量。绝对零隐私收集,仅作原子累加,底层网关源码全开源,接受全网极客审计。
-**丝滑战术交互 (Seamless UI)**:司令部交互面板像素级打磨。新节点发送暗号入伍成功后,司令部将无缝零延迟自动呼出最新的活跃节点阵列面板,彻底免除重复输入命令的繁琐,掌控感拉满。
## 📂 项目架构 (Monorepo)
@@ -35,17 +49,16 @@
┣ 📂 core/ # 🛡️ 边缘哨兵Webhook 被动监听、哈希锚定执行引擎
┣ 📂 scripts/ # 🐍 兵工厂引擎:基于 Python 的多物理分区 UA 生成器
┣ 📂 data/ # 🗂️ 全球数据规则库 (动态拓扑)
┃ ┣ 📜 map.json # 🌍 全球区域大脑 (v3.5.0 大洲战区拓扑)
┃ ┣ 📜 map.json # 🌐 全球区域索引大脑 (Master Index)
┃ ┣ 📂 regions/ # 🧊 冷数据:按 [国家/省州/城市] 深度细分的 LBS 锚点
┃ ┣ 📂 keywords/ # 🔥 热数据:按国家归类的动态搜索词库 (OTA 自动更新)
┃ ┗ 📜 user_agents.txt # 🔥 热数据:由兵工厂每月锻造的绝对坐标专属设备库
┣ 📜 version.txt # 🚩 双端版本信标Agent/Master 独立解耦的 KV 环境配置 (v3.5.1)
┗ 📂 telemetry/ # 👁️‍🗨️ 玻璃房计划Cloudflare Workers 透明计数器网关源码
```
## 🚀 极速部署 (Quick Start)
v3.5.x 提供了两种接入模式,请根据您的战术需求选择:
v3.2.x 提供了两种接入模式,请根据您的战术需求选择:
### 🔹 模式 A官方公共模式 (最简、推荐)
**适合不想折腾、只想快速养护 IP 的新兵。**
@@ -73,18 +86,13 @@ bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/i
```
3. **激活节点**:同上,将暗号转发给您自己的机器人即可。
### ⚠️ 架构级热升级指引 (Upgrade to v3.5.0)
### ⚠️ 平滑升级指引 (Upgrade to v3.2.2)
得益于 **v3.5.0 全新引入的 SSOT 版本锚点与状态机路由**,系统升级现已变得极其智能化
得益于 **v3.2.2 全新引入的平滑热更新引擎 (Smooth Upgrade Engine)**,系统升级现已变得极其优雅与安全
**如果您是从远古旧版 (v3.3.1 / v3.3.2) 升级:**
1. 在终端再次运行对应的官方部署指令。
2. 脚本会识别到您处于“前版本锚点时代”,会自动为您执行【跨代架构重组】。
3. **关键动作**:由于节点命名防撞机制变更,升级后您的 TG 会收到一条新的 `#REGISTER#` 指令,请点击并发送一次以同步新身份。
4. **清理**:在面板中手动剔除失联的旧节点即可。
无需卸载旧版本,无论您是要升级 Agent 边缘节点还是 Master 控制中枢,只需在您的终端中**再次运行上方对应的官方部署指令**。
**如果您已处于 v3.4.0+**
所有的升级已进入**“极致静默平滑模式”**。安装引擎会动态抓取云端 `version.txt`,自动修正本地 `config.conf` 的版本号一键回车3 秒即可完成全系组件的热重载换代!
安装雷达会自动嗅探您的历史部署状态(包括您的 Token、区域设定、SQLite 数据库及物理网卡锚点)。当询问是否平滑升级时,您只需**一路回车 (默认选 y)**,脚本将在短短 3 秒内瞬间完成核心装甲的无损换脑手术,您的所有战术资产将得到 100% 保留!
🗑️ 一键无痕卸载
如果你需要清理某个边缘节点,只需重新运行 `core/install.sh` 并选择 **[2]**,或直接在节点终端执行:

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 - 动态锚点版)
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 V3.0.3)
# 核心功能: 智能防打扰注册、进程自检、模块级路由分发(403拦截)
# ==========================================================
@@ -17,30 +17,21 @@ source "$CONFIG_FILE"
# 默认 Webhook 监听端口
AGENT_PORT=${AGENT_PORT:-9527}
# [v3.5.2 核心] 载入不可变主键与可变展示名 (双轨身份)
if [ -z "$NODE_NAME" ]; then
IP_HASH=$(echo "${PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${IP_HASH}"
fi
NODE_ALIAS="${NODE_ALIAS:-$NODE_NAME}"
NODE_NAME=$(hostname | cut -c 1-15)
# --- [重点升级 1: 守护进程防冲突自检] ---
if pgrep -f "webhook.py $AGENT_PORT" > /dev/null; then
exit 0
fi
# 1. 尝试获取实时公网 IP
# 1. [v3.0.1修复] 严格按照 install.sh 锁定的网络协议 (v4/v6) 获取 IP
RAW_IP=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip | tr -d '[:space:]')
# [v3.3.1 修改] 为新获取到的 v6 自动加方括号;如果网络波动没抓到,强制信任本地 config 中的公网面孔
if [ -n "$RAW_IP" ]; then
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
AGENT_IP="[${RAW_IP}]"
else
AGENT_IP="$RAW_IP"
fi
# 为新获取到的 v6 自动加方括号,以确保与之前锁定的格式对齐比对
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
AGENT_IP="[${RAW_IP}]"
else
AGENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
AGENT_IP="$RAW_IP"
fi
if [ -n "$AGENT_IP" ]; then
@@ -50,8 +41,8 @@ if [ -n "$AGENT_IP" ]; then
# 只有当这是第一次运行,或者公网 IP 发生变动时,才发送 Telegram 申请
if [ "$AGENT_IP" != "$LAST_IP" ]; then
# [v3.5.2 核心] 携带 6 字段双轨身份发起注册申请 (展示别名,暗号尾部追加 NODE_ALIAS)
REG_MSG="👋 **[边缘节点接入申请]**%0A大区: \`${REGION_CODE}\`%0A节点: \`${NODE_ALIAS}\`%0A地址: \`${AGENT_IP}:${AGENT_PORT}\`%0A%0A⚠ **安全验证**: 为防止非法节点接入,请长按复制下方代码,并**发送给我**以完成最终授权录入:%0A%0A\`#REGISTER#|${REGION_CODE}|${NODE_NAME}|${AGENT_IP}|${AGENT_PORT}|${NODE_ALIAS}\`"
# V3.1.3 协议升级: 在底部暗号中精准嵌入 ${REGION_CODE} 大区标识
REG_MSG="👋 **[边缘节点接入申请]**%0A大区: \`${REGION_CODE}\`%0A节点: \`${NODE_NAME}\`%0A地址: \`${AGENT_IP}:${AGENT_PORT}\`%0A%0A⚠ **安全验证**: 为防止非法节点接入,请长按复制下方代码,并**发送给我**以完成最终授权录入:%0A%0A\`#REGISTER#|${REGION_CODE}|${NODE_NAME}|${AGENT_IP}|${AGENT_PORT}\`"
curl -s -m 5 -X POST "${TG_API_URL}" \
-d "chat_id=${CHAT_ID}" \
@@ -124,7 +115,7 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
return
# 校验 3HMAC 数据完整性与身份合法性校验
msg = f"{req_path}:{req_t}".encode('utf-8')
msg = "{}:{}".format(req_path, req_t).encode('utf-8')
expected_sign = hmac.new(AUTH_TOKEN.encode('utf-8'), msg, hashlib.sha256).hexdigest()
# 使用 compare_digest 防御时序攻击
@@ -209,11 +200,8 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
if lines:
log_data = html.escape("".join(lines[-15:]))
# [v3.5.2 核心] 获取版本与节点展示别名
local_ver = config.get('AGENT_VERSION', '未知')
node_alias = config.get('NODE_ALIAS', config.get('NODE_NAME', 'Unknown-Node'))
text_msg = f"📄 <b>[{node_alias}] 实时日志 (v{local_ver}):</b>\n<pre><code>{log_data}</code></pre>"
node_name = subprocess.check_output(['hostname']).decode('utf-8').strip()[:15]
text_msg = "📄 <b>[{}] 实时运行日志:</b>\n<pre><code>{}</code></pre>".format(node_name, log_data)
data = urllib.parse.urlencode({
'chat_id': config.get('CHAT_ID', ''),
@@ -224,122 +212,13 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
req = urllib.request.Request(
config.get('TG_API_URL', ''),
data=data,
# [动态化] 彻底消灭硬编码,使用运行态版本号
headers={'User-Agent': f'IP-Sentinel-Agent/{local_ver}'}
headers={'User-Agent': 'IP-Sentinel-Agent/3.0.4'}
)
urllib.request.urlopen(req, timeout=10)
except Exception as e:
print(f"Log transmission failed: {e}")
# 路由 5: 节点重命名展示别名同步接口 (Base64 终极防御版)
elif req_path == '/trigger_rename':
b64_alias = query.get('b64', [''])[0]
if not b64_alias:
self.send_response(400)
self.end_headers()
self.wfile.write(b"400 Bad Request: Alias is empty\n")
return
import re
import base64
try:
# 1. 还原 URL 安全的 Base64 字符并解码 (杜绝乱码与 WAF 拦截)
pad = len(b64_alias) % 4
if pad > 0:
b64_alias += '=' * (4 - pad)
b64_alias = b64_alias.replace('-', '+').replace('_', '/')
raw_alias = base64.b64decode(b64_alias).decode('utf-8', errors='ignore')
# 2. 强清洗:杜绝 TG Markdown 崩溃严格限制中英数最大20字符
decoded_alias = raw_alias.replace('_', '-')
safe_alias = re.sub(r'[^a-zA-Z0-9\-\u4e00-\u9fa5]', '', decoded_alias)[:20]
if safe_alias:
# 3. 强容错读写 config.conf (引入 fcntl 排他锁与 r+ 模式防并发清空)
config_path = '/opt/ip_sentinel/config.conf'
import fcntl
with open(config_path, 'r+', encoding='utf-8', errors='ignore') as f:
fcntl.flock(f, fcntl.LOCK_EX)
lines = f.readlines()
alias_found = False
for i, line in enumerate(lines):
if line.startswith('NODE_ALIAS='):
lines[i] = f'NODE_ALIAS="{safe_alias}"\n'
alias_found = True
break
if not alias_found:
lines.append(f'NODE_ALIAS="{safe_alias}"\n')
f.seek(0)
f.writelines(lines)
f.truncate()
fcntl.flock(f, fcntl.LOCK_UN)
# [v3.5.2 极致丝滑] 移除向 TG 推送冗余报文的逻辑,直接向 Master 回执成功状态即可
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Action Accepted: trigger_rename\n")
return
except Exception as e:
self.send_response(500)
self.end_headers()
self.wfile.write(f"500 Internal Error: {str(e)}\n".encode('utf-8'))
return
print("Log transmission failed: {}".format(e))
self.send_response(400)
self.end_headers()
self.wfile.write(b"400 Bad Request: Invalid Characters\n")
# ================== [v3.5.3 新增: 模块动态启停接口] ==================
elif req_path == '/trigger_toggle':
mod_name = query.get('mod', [''])[0]
target_state = query.get('state', [''])[0].lower()
if mod_name not in ['google', 'trust'] or target_state not in ['true', 'false']:
self.send_response(400)
self.end_headers()
self.wfile.write(b"400 Bad Request: Invalid parameters\n")
return
config_key = f"ENABLE_{mod_name.upper()}="
try:
config_path = '/opt/ip_sentinel/config.conf'
import fcntl
with open(config_path, 'r+', encoding='utf-8', errors='ignore') as f:
fcntl.flock(f, fcntl.LOCK_EX)
lines = f.readlines()
found = False
for i, line in enumerate(lines):
if line.startswith(config_key):
lines[i] = f'{config_key}"{target_state}"\n'
found = True
break
if not found:
lines.append(f'{config_key}"{target_state}"\n')
f.seek(0)
f.writelines(lines)
f.truncate()
fcntl.flock(f, fcntl.LOCK_UN)
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Action Accepted: trigger_toggle\n")
except Exception as e:
self.send_response(500)
self.end_headers()
self.wfile.write(f"500 Internal Error: {str(e)}\n".encode('utf-8'))
else:
self.send_response(404)
self.end_headers()
@@ -355,8 +234,8 @@ class ThreadedDualStackServer(socketserver.ThreadingMixIn, socketserver.TCPServe
try:
bind_addr = "::" if socket.has_ipv6 else ""
with ThreadedDualStackServer((bind_addr, PORT), AgentHandler) as httpd:
httpd.serve_forever()
httpd = ThreadedDualStackServer((bind_addr, PORT), AgentHandler)
httpd.serve_forever()
except Exception as e:
sys.exit(1)
# ====================================================================================

View File

@@ -1,97 +1,43 @@
#!/bin/bash
# ==========================================================
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 - 动态锚点版)
# 核心功能: 战区分组菜单、模块按需开启、官方机器人一键配置、版本状态机路由
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 v3.3.0 - OTA 活体引擎)
# 核心功能: 区域选择、模块按需开启、官方机器人一键配置、平滑热更新、分频错峰调度
# ==========================================================
# 你的 GitHub 仓库 Raw 数据直链前缀
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy"
# 临时改为私库地址用于测试
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
INSTALL_DIR="/opt/ip_sentinel"
CONFIG_FILE="${INSTALL_DIR}/config.conf"
# [核心: 动态提取 Agent 专属版本锚点 (KV 解析法)]
TARGET_VERSION=$(curl -s -m 3 "${REPO_RAW_URL}/version.txt" | grep "^AGENT_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
# 🛡️ 兜底防线:如果网络波动拉取失败,启用内置的安全兜底版本
TARGET_VERSION=${TARGET_VERSION:-"3.5.1"}
echo "========================================================"
echo " 🛡️ 欢迎使用 IP-Sentinel (边缘节点 Edge Agent)"
echo "========================================================"
# 轻量级版本号比对函数 (例如: version_lt "3.3.1" "3.4.0" 返回 true)
version_lt() {
test "$(printf '%s\n' "$1" "$2" | sort -V | head -n 1)" = "$1" && test "$1" != "$2"
}
# 1. 依赖检查与安装 (新增 python3 用于轻量级 Webhook 服务)
echo -e "\n[1/7] 正在安装必要环境依赖 (curl, jq, cron, procps, python3)..."
# 1. 依赖检查与智能安装 (v3.5.4 兼容性升级: 支持 Alpine, Arch 及更完善的依赖链)
echo -e "\n[1/7] 正在探测并安装基础环境依赖 (curl, jq, cron, procps, python3)..."
# 定义必须检测的核心命令
REQUIRED_CMDS=("curl" "jq" "crontab" "pgrep" "python3")
MISSING_CMDS=()
# 基础探测:预检查缺失的命令
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
MISSING_CMDS+=("$cmd")
fi
done
# 如果有缺失,执行智能安装逻辑
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
echo "⏳ 发现缺失依赖: ${MISSING_CMDS[*]},正在尝试自动补齐..."
# 嗅探包管理器
if command -v apt-get >/dev/null 2>&1; then
# Debian / Ubuntu 系列
apt-get update -y >/dev/null 2>&1
apt-get install -y curl jq cron procps python3 >/dev/null 2>&1
systemctl enable cron >/dev/null 2>&1 && systemctl start cron >/dev/null 2>&1
elif command -v yum >/dev/null 2>&1 || command -v dnf >/dev/null 2>&1; then
# RHEL / CentOS / AlmaLinux 系列
PKG_MGR="yum"
command -v dnf >/dev/null 2>&1 && PKG_MGR="dnf"
$PKG_MGR install -y curl jq cronie procps-ng python3 >/dev/null 2>&1
systemctl enable crond >/dev/null 2>&1 && systemctl start crond >/dev/null 2>&1
elif command -v apk >/dev/null 2>&1; then
# [核心修复 Issue #21] Alpine Linux 系列
echo "Alpine 探测到系统类型为 Alpine Linux正在执行轻量级安装..."
apk add --no-cache curl jq dcron procps python3 bash >/dev/null 2>&1
# Alpine 下必须手动创建 cron spool 目录并启动 crond
mkdir -p /var/spool/cron/crontabs
rc-update add crond default >/dev/null 2>&1
service crond start >/dev/null 2>&1
elif command -v pacman >/dev/null 2>&1; then
# [核心修复 Issue #250] Arch Linux 系列
pacman -Sy --noconfirm curl jq cronie procps-ng python >/dev/null 2>&1
# Arch 下某些 cronie 实现可能缺少 /root/.cache 权限,做个兼容保障
mkdir -p /root/.cache/crontab 2>/dev/null
systemctl enable cronie >/dev/null 2>&1 && systemctl start cronie >/dev/null 2>&1
else
# 无法识别的系统:退出并给出清晰的引导信息
echo -e "\033[31m❌ 自动安装失败:系统未知的包管理器。\033[0m"
echo -e "\033[33m⚠ 请根据您的操作系统,手动执行以下安装命令后重新运行本脚本:\033[0m"
echo -e " Debian/Ubuntu: \033[36mapt-get update && apt-get install -y curl jq cron procps python3\033[0m"
echo -e " CentOS/RHEL: \033[36myum install -y curl jq cronie procps-ng python3\033[0m"
echo -e " Alpine Linux: \033[36mapk add --no-cache curl jq dcron procps python3 bash\033[0m"
echo -e " Arch Linux: \033[36mpacman -Sy curl jq cronie procps-ng python\033[0m"
exit 1
fi
# 安装后二次复检
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo -e "\033[31m❌ 致命错误:核心命令 '$cmd' 仍未找到!\033[0m"
echo -e "这通常是因为您的系统源配置错误或缺失基础组件库导致。"
echo -e "请手动修复您的包管理器源,或联系 VPS 供应商重新格式化系统。"
exit 1
fi
done
# ================== [Legacy: Debian 9 APT 源抢修补丁] ==================
if [ -f /etc/debian_version ] && grep -q -E "^9\." /etc/debian_version; then
echo -e "\033[33m⚠ 检测到 Debian 9 (Stretch),正在抢修已停用的 APT 档案馆源...\033[0m"
echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list
echo "deb http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list
sed -i '/stretch-updates/d' /etc/apt/sources.list
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until
fi
# =======================================================================
if [ -f /etc/debian_version ]; then
apt-get update -y >/dev/null 2>&1
apt-get install -y curl jq cron procps python3 >/dev/null 2>&1
elif [ -f /etc/redhat-release ]; then
yum install -y curl jq cronie procps-ng python3 >/dev/null 2>&1
systemctl enable crond && systemctl start crond
else
echo "⚠️ 未知系统,请确保已手动安装 curl, jq, pgrep 和 python3"
fi
echo -e "\033[32m✅ 基础环境检测通过。\033[0m"
# 2. 交互式引导与动态地图解析 (v3.0 全球网络)
echo -e "\n[2/7] 正在连线云端,拉取全球节点地图..."
@@ -107,9 +53,6 @@ echo " 1) 🚀 部署边缘节点 (进入全球节点配置)"
echo " 2) 🗑️ 一键卸载 IP-Sentinel"
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定
ACTION_CHOICE=${ACTION_CHOICE:-1}
if [ "$ACTION_CHOICE" == "2" ]; then
echo -e "\n⏳ 正在拉取卸载程序..."
curl -sL "${REPO_RAW_URL}/core/uninstall.sh" -o "/tmp/ip_uninstall.sh"
@@ -149,10 +92,12 @@ pkill -9 -f "webhook.py" >/dev/null 2>&1 || true
pkill -9 -f "agent_daemon.sh" >/dev/null 2>&1 || true
pkill -9 -f "runner.sh" >/dev/null 2>&1 || true
# 2. 清除系统定时任务 (Cron) 中的旧版条目 (安全容错版)
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_clean || true
[ -f /tmp/cron_clean ] && crontab /tmp/cron_clean 2>/dev/null
rm -f /tmp/cron_clean
# 2. 清除系统定时任务 (Cron) 中的旧版条目
if crontab -l >/dev/null 2>&1; then
crontab -l | grep -v "ip_sentinel" > /tmp/cron_clean
crontab /tmp/cron_clean
rm -f /tmp/cron_clean
fi
# 3. 抹除旧版核心代码,杜绝代码冲突 (根据模式分流)
if [ "$UPGRADE_MODE" == "true" ]; then
@@ -178,23 +123,9 @@ echo -e "\033[32m✅ 环境清理完毕,幽灵进程已肃清!\033[0m"
# ==========================================================
if [ "$UPGRADE_MODE" == "false" ]; then
# 📍 动态级菜单:战区(大洲)选择
echo -e "\n\033[36m📍 【第级】请选择目标战区 (Continent):\033[0m"
jq -r '.continents[] | "\(.id)|\(.name)"' /tmp/map.json > /tmp/continents.txt
i=1; CONT_MAP=()
while IFS="|" read -r cont_id cont_name; do
echo " $i) $cont_name"
CONT_MAP[$i]="$cont_id"
((i++))
done < /tmp/continents.txt
read -p "请输入选择 [1-$((i-1))] (默认1): " CONT_SEL
CONT_SEL=${CONT_SEL:-1}
CONT_ID="${CONT_MAP[$CONT_SEL]}"
# 📍 动态一级菜单:国家选择 (基于选中战区)
echo -e "\n\033[36m📍 【第一级】正在检索 [$CONT_ID] 战区下的国家/地区...\033[0m"
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | \"\(.id)|\(.name)|\(.keyword_file)\"" /tmp/map.json > /tmp/countries.txt
# 📍 动态级菜单:国家选择
echo -e "\n\033[36m📍 【第级】请选择目标国家/地区:\033[0m"
jq -r '.countries[] | "\(.id)|\(.name)|\(.keyword_file)"' /tmp/map.json > /tmp/countries.txt
i=1; COUNTRY_MAP=(); KEYWORD_MAP=()
while IFS="|" read -r c_id c_name k_file; do
echo " $i) $c_name"
@@ -209,9 +140,9 @@ if [ "$UPGRADE_MODE" == "false" ]; then
KEYWORD_FILE="${KEYWORD_MAP[$C_SEL]}"
REGION_CODE="$COUNTRY_ID" # 兼容旧版的 config.conf
# 📍 动态二级菜单:省/州选择 (基于选中战区和国家)
# 📍 动态二级菜单:省/州选择
echo -e "\n\033[36m📍 【第二级】正在检索 [$COUNTRY_ID] 的行政区数据...\033[0m"
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/states.txt
jq -r ".countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/states.txt
STATE_COUNT=$(wc -l < /tmp/states.txt)
if [ "$STATE_COUNT" -eq 1 ]; then
@@ -229,9 +160,9 @@ if [ "$UPGRADE_MODE" == "false" ]; then
STATE_ID="${STATE_MAP[$S_SEL]}"
fi
# 📍 动态三级菜单:城市选择 (基于战区、国家、州三层过滤)
# 📍 动态三级菜单:城市选择
echo -e "\n\033[36m📍 【第三级】请锁定具体城市节点:\033[0m"
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | select(.id==\"$STATE_ID\") | .cities[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/cities.txt
jq -r ".countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | select(.id==\"$STATE_ID\") | .cities[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/cities.txt
CITY_COUNT=$(wc -l < /tmp/cities.txt)
if [ "$CITY_COUNT" -eq 1 ]; then
@@ -249,8 +180,8 @@ if [ "$UPGRADE_MODE" == "false" ]; then
CITY_ID="${CITY_MAP[$CI_SEL]}"
fi
# 清理临时文件 (增加清理 continents.txt)
rm -f /tmp/map.json /tmp/continents.txt /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
# 清理临时文件
rm -f /tmp/map.json /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
# 本地工作目录初始化 (支持 v3.0 的深度层级)
mkdir -p "${INSTALL_DIR}/core"
@@ -258,46 +189,46 @@ if [ "$UPGRADE_MODE" == "false" ]; then
mkdir -p "${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}"
mkdir -p "${INSTALL_DIR}/logs"
# 3. 功能模块前置开关 (v3.5.3 默认全量加载,后续经由 TG 动态启停)
echo -e "\n[3/7] 正在初始化养护模块 (默认全量部署,支持 TG 远程动态启停)..."
# 3. 功能模块前置开关 (按需加载)
echo -e "\n[3/7] 请选择需要开启的养护模块 (按需开启,节省资源):"
echo " 1) 📍 仅开启 [Google 区域纠偏] (默认,适合流媒体解锁机位漂移)"
echo " 2) 🛡️ 仅开启 [IP 信用净化] (适合高风险机房 IP 降低 Scamalytics 分数)"
echo " 3) 🔥 双管齐下 (同时开启以上两项)"
read -p "请输入选择 [1-3] (默认1): " MODULE_CHOICE
ENABLE_GOOGLE="true"
ENABLE_TRUST="true"
ENABLE_TRUST="false"
case ${MODULE_CHOICE:-1} in
2) ENABLE_GOOGLE="false"; ENABLE_TRUST="true" ;;
3) ENABLE_GOOGLE="true"; ENABLE_TRUST="true" ;;
*) ENABLE_GOOGLE="true"; ENABLE_TRUST="false" ;;
esac
# 4. 接入 Master 中枢配置
echo -e "\n[4/7] 是否接入 Master 司令部进行远程联控? (y/n)"
echo -e "\n[4/7] 是否接入 Master 司令部(需要配置与主控相同的 TG 机器人) (y/n)"
read -p "请输入选择 [y/n] (默认n): " TG_CHOICE
TG_TOKEN=""
CHAT_ID=""
AGENT_PORT="9527"
if [[ "$TG_CHOICE" =~ ^[Yy]$ ]]; then
echo -e "\n请选择中枢接入模式 (推荐私有部署,支持后续 OTA 远程静默升级):"
echo " 1) 🛡️ 私有独立中枢 (需提供自建 Bot Token推荐)"
echo " 2) ☁️ 官方公共网关 (@OmniBeacon_bot新手免配置)"
read -p "请输入选择 [1-2] (默认1): " MASTER_TYPE
MASTER_TYPE=${MASTER_TYPE:-1}
echo -e "\n\033[33m💡 提示:您可以选择使用自己的机器人,或者直接回车使用官方公共机器人。\033[0m"
echo -e "\033[33m⚠ 注意:若使用官方机器人,请务必先在 TG 中关注 @OmniBeacon_bot 并发送 /start\033[0m"
if [ "$MASTER_TYPE" == "2" ]; then
read -p "请输入您的 Telegram Bot Token (回车使用官方默认): " USER_TOKEN
if [ -z "$USER_TOKEN" ]; then
TG_TOKEN="OFFICIAL_GATEWAY_MODE"
TG_API_URL="https://omni-gateway.samanthaestime296.workers.dev"
echo -e "\033[32m✅ 已自动连接官方安全网关 (@OmniBeacon_bot)。\033[0m"
echo -e "\033[33m👉 请确保您已在 TG 中关注官方机器人并发送过 /start否则将无法接收消息。\033[0m"
echo -e "\033[33m👉 请确保您已关注官方机器人并发送过 /start否则将无法接收消息。\033[0m"
else
echo -e "\n\033[36m📘 私有 Bot 创建教程: https://blog.iot-architect.com/engineering-practice/create-private-telegram-bot-via-botfather/\033[0m"
read -p "请输入您的私有 Telegram Bot Token: " USER_TOKEN
# 🛡️ 核心防误触修复:拦截空回车或粘贴换行导致的跳过 Bug
while [ -z "$USER_TOKEN" ]; do
read -p "⚠️ Token 不能为空,请重新输入您的 Bot Token: " USER_TOKEN
done
TG_TOKEN="$USER_TOKEN"
TG_API_URL="https://api.telegram.org/bot${TG_TOKEN}/sendMessage"
echo -e "\033[32m✅ 已记录您的私有机器人 Token。\033[0m"
fi
echo -e "\n\033[33m💡 提示:如果您不知道下方自己的 Chat ID 是什么,可以关注 @userinfobot 获取。\033[0m"
echo -e "\033[36m📘 查看图文教程: https://blog.iot-architect.com/engineering-practice/get-telegram-personal-id-via-userinfobot/\033[0m"
read -p "请输入你的 Chat ID (必须准确,否则无法联控): " CHAT_ID
echo -e "\033[33m💡 提示:如果您不知道自己的 Chat ID可以关注 @userinfobot 获取。\033[0m"
read -p "请输入你的 Chat ID (与主控一致): " CHAT_ID
# ================== [v3.0.3 变更: 智能随机高位端口生成系统] ==================
echo -e "\n\033[36m[4.2/7] 正在构建 Webhook 安全通信隧道...\033[0m"
@@ -387,53 +318,13 @@ if [ "$UPGRADE_MODE" == "false" ]; then
fi
fi
# ================== [v3.3.1 核心重构: 身份剥离与双栈实弹嗅探] ==================
# 1. 固化对外通讯身份 (自动穿透方括号护甲)
# 终极修复:为 IPv6 自动穿上防护装甲(方括号),解决 Master 拼接 URL 报错问题
if [[ "$PUBLIC_IP" == *":"* ]] && [[ "$PUBLIC_IP" != *"["* ]]; then
SAFE_PUBLIC_IP="[${PUBLIC_IP}]"
BIND_IP="[${PUBLIC_IP}]"
else
SAFE_PUBLIC_IP="$PUBLIC_IP"
fi
# 2. 实弹打靶测试 (NAT 环境嗅探与双栈自适应)
echo -n "🕵️ 正在进行出站链路试射 (NAT环境与双栈嗅探)..."
RAW_TEST_IP=$(echo "$SAFE_PUBLIC_IP" | tr -d '[]')
# 智能切换靶机V6 机器打 Cloudflare V6 节点V4 机器打 1.1.1.1
if [[ "$RAW_TEST_IP" == *":"* ]]; then
TEST_TARGET="https://[2606:4700:4700::1111]"
else
TEST_TARGET="https://1.1.1.1"
fi
# 执行实弹试射
if curl --interface "$RAW_TEST_IP" -sI -m 3 "$TEST_TARGET" >/dev/null 2>&1; then
echo -e " \033[32m✅ 原生直连,物理网卡死锁已激活。\033[0m"
BIND_IP="$SAFE_PUBLIC_IP"
else
echo -e " \033[33m⚠ 发现 NAT/虚拟路由架构,自动卸除网卡枷锁,交由内核路由。\033[0m"
BIND_IP=""
fi
echo -e "\033[32m✅ 哨兵对外联络点已永久锁定至: $SAFE_PUBLIC_IP\033[0m"
# ========================================================================
# ================== [v3.5.2 新增: 节点不可变主键与展示别名] ==================
IP_HASH=$(echo "${SAFE_PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${IP_HASH}"
NODE_ALIAS="$NODE_NAME"
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
echo -e "\n\033[36m[4.8/7] 节点展示别名设定 (用于面板友好显示)...\033[0m"
echo -e "💡 系统底层的不可变主键为: \033[33m${NODE_NAME}\033[0m"
read -p "请输入节点展示别名 (如'纽约机房', 回车使用默认): " CUSTOM_ALIAS
if [ -n "$CUSTOM_ALIAS" ]; then
# 🛡️ 强制字符清洗:防御 Shell 注入,并限制长度防刷屏
NODE_ALIAS=$(echo "$CUSTOM_ALIAS" | tr -d '"'\''\`\$\|&;<>\n\r' | cut -c 1-20)
[ -z "$NODE_ALIAS" ] && NODE_ALIAS="$NODE_NAME"
fi
echo -e "✅ 已锁定节点展示别名: \033[32m$NODE_ALIAS\033[0m"
BIND_IP="$PUBLIC_IP"
fi
echo -e "\033[32m✅ 哨兵锚点已永久锁定至: $BIND_IP\033[0m"
# ========================================================================
# 5. 远程拉取冷数据并解析固化
@@ -453,10 +344,9 @@ if [ "$UPGRADE_MODE" == "false" ]; then
LANG_PARAMS=$(jq -r '.google_module.lang_params' "$REGION_JSON_FILE")
VALID_URL_SUFFIX=$(jq -r '.google_module.valid_url_suffix' "$REGION_JSON_FILE")
# 写入本地静态配置文件 (v3.4.0 引入版本锚点)
# 写入本地静态配置文件
cat > "$CONFIG_FILE" << EOF
# IP-Sentinel 本地固化配置 (生成时间: $(date '+%Y-%m-%d %H:%M:%S'))
AGENT_VERSION="$TARGET_VERSION"
REGION_CODE="$REGION_CODE"
REGION_NAME="$REGION_NAME"
BASE_LAT="$BASE_LAT"
@@ -475,14 +365,9 @@ AGENT_PORT="$AGENT_PORT"
INSTALL_DIR="$INSTALL_DIR"
LOG_FILE="${INSTALL_DIR}/logs/sentinel.log"
# [v3.3.1修改: 双核身份剥离配置]
# [v3.0.1新增修改 2: 网络栈锚点锁定配置,供其他脚本读取]
IP_PREF="$IP_PREF"
PUBLIC_IP="$SAFE_PUBLIC_IP"
BIND_IP="$BIND_IP"
# [v3.5.2新增: 双轨身份系统]
NODE_NAME="$NODE_NAME"
NODE_ALIAS="$NODE_ALIAS"
EOF
# ================== [v3.0.3 变更: 敏感配置文件权限收敛] ==================
@@ -492,61 +377,6 @@ EOF
fi
# 🛑 拦截块结束 (全套交互配置跳过完毕)
# ================== [v3.3.1 核心修复: 老节点配置无损热迁移] ==================
if [ "$UPGRADE_MODE" == "true" ]; then
if ! grep -q "PUBLIC_IP=" "$CONFIG_FILE"; then
echo -e "\n🔄 [平滑迁移] 正在对老节点进行 v3.3.1 双核身份架构升级..."
# 重新抓取公网面孔 (应对老节点 BIND_IP 可能已被手动清空的情况)
MIGRATE_IP=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip | tr -d '[:space:]')
[[ "$MIGRATE_IP" == *":"* ]] && [[ "$MIGRATE_IP" != *"["* ]] && MIGRATE_IP="[${MIGRATE_IP}]"
echo -n "🕵️ 正在进行补发链路试射 (NAT与双栈嗅探)..."
RAW_TEST_IP=$(echo "$MIGRATE_IP" | tr -d '[]')
if [[ "$RAW_TEST_IP" == *":"* ]]; then
TEST_TARGET="https://[2606:4700:4700::1111]"
else
TEST_TARGET="https://1.1.1.1"
fi
if curl --interface "$RAW_TEST_IP" -sI -m 3 "$TEST_TARGET" >/dev/null 2>&1; then
echo -e " \033[32m✅ 原生直连,网卡死锁已继承。\033[0m"
NEW_BIND_IP="$MIGRATE_IP"
else
echo -e " \033[33m⚠ 发现 NAT 架构,已自动卸除老版本的物理枷锁。\033[0m"
NEW_BIND_IP=""
fi
# 动态修改旧配置文件 (更新 BIND_IP追加 PUBLIC_IP)
sed -i "s/^BIND_IP=.*/BIND_IP=\"$NEW_BIND_IP\"/" "$CONFIG_FILE"
echo "PUBLIC_IP=\"$MIGRATE_IP\"" >> "$CONFIG_FILE"
# 刷新当前安装脚本的环境变量,防止底部代码报错
SAFE_PUBLIC_IP="$MIGRATE_IP"
BIND_IP="$NEW_BIND_IP"
else
# 如果是未来再升级,配置文件已是最新,直接提取变量供安装脚本尾部使用
SAFE_PUBLIC_IP=$(grep "^PUBLIC_IP=" "$CONFIG_FILE" | cut -d'"' -f2)
fi
# [v3.5.2 热修复] 兼容老版本没有 NODE_NAME 和 NODE_ALIAS 的情况,无损补齐
if ! grep -q "^NODE_NAME=" "$CONFIG_FILE"; then
TMP_HASH=$(echo "${SAFE_PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${TMP_HASH}"
NODE_ALIAS="$NODE_NAME"
echo "NODE_NAME=\"$NODE_NAME\"" >> "$CONFIG_FILE"
echo "NODE_ALIAS=\"$NODE_ALIAS\"" >> "$CONFIG_FILE"
else
NODE_NAME=$(grep "^NODE_NAME=" "$CONFIG_FILE" | cut -d'"' -f2)
NODE_ALIAS=$(grep "^NODE_ALIAS=" "$CONFIG_FILE" | cut -d'"' -f2)
if [ -z "$NODE_ALIAS" ]; then
NODE_ALIAS="$NODE_NAME"
echo "NODE_ALIAS=\"$NODE_ALIAS\"" >> "$CONFIG_FILE"
fi
fi
fi
# ========================================================================
# 6. 拉取全套组件 (按需下载,绝不浪费空间)
echo -e "\n[6/7] 正在根据模块开关部署核心引擎与热数据..."
# 确保目录在升级模式下也能被正确建立
@@ -581,7 +411,7 @@ chmod +x ${INSTALL_DIR}/core/*.sh
# 7. 配置系统定时任务 (高频调度与看门狗)
echo -e "\n[7/7] 正在注入系统定时任务与看门狗进程..."
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_backup || true
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_backup
# 核心养护模块: 每 30 分钟触发一次
echo "*/30 * * * * ${INSTALL_DIR}/core/runner.sh >/dev/null 2>&1" >> /tmp/cron_backup
@@ -598,8 +428,8 @@ if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
# [v3.0.1新增修改 3: 删除原来的 curl 取 IP直接使用我们上方锁定的 BIND_IP]
# 并提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的重复推送
# [修复竞态]: 提前写入公网 IP 缓存,彻底阻断 agent_daemon 首次启动时的抢跑推送
echo "$SAFE_PUBLIC_IP" > "${INSTALL_DIR}/core/.last_ip"
# [修复竞态]: 提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的抢跑推送
echo "$BIND_IP" > "${INSTALL_DIR}/core/.last_ip"
# 双保险守护进程看门狗
echo "@reboot nohup bash ${INSTALL_DIR}/core/agent_daemon.sh >/dev/null 2>&1 &" >> /tmp/cron_backup
@@ -609,66 +439,35 @@ if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
nohup bash "${INSTALL_DIR}/core/agent_daemon.sh" >/dev/null 2>&1 &
fi
[ -f /tmp/cron_backup ] && crontab /tmp/cron_backup 2>/dev/null
crontab /tmp/cron_backup
rm -f /tmp/cron_backup
# ================== [v3.4.0 核心: 状态机驱动的热更新路由] ==================
# ================== [v3.2.2 优化: 战报通知分流 (注册/升级)] ==================
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
# [v3.5.2 核心] 发送携带双轨身份的注册指令 (追加 NODE_ALIAS 作为第 6 个字段)
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${SAFE_PUBLIC_IP}|${AGENT_PORT}|${NODE_ALIAS}"
NODE_NAME=$(hostname | cut -c 1-15)
if [ "$UPGRADE_MODE" == "true" ]; then
# 读取本地老版本号,如果没有则视为远古版本 v3.3.1
OLD_VERSION=$(grep "^AGENT_VERSION=" "$CONFIG_FILE" | cut -d'"' -f2)
[ -z "$OLD_VERSION" ] && OLD_VERSION="3.3.1"
# [路由表 1]: 跨代兼容 (老版本 < v3.3.2)
# 必须强制下发带有 #REGISTER# 的警告,引导长官重新同步哈希身份
if version_lt "$OLD_VERSION" "3.3.2"; then
echo -e "\n📡 [路由枢纽] 正在执行跨代架构重组 (v${OLD_VERSION} -> v${TARGET_VERSION})..."
curl -s -X POST "${TG_API_URL}" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=✨ *IP-Sentinel 引擎热更新完成!*
📍 节点:\`${NODE_ALIAS}\`
🌐 IP\`${SAFE_PUBLIC_IP}\`
🚀 状态v${TARGET_VERSION} OTA 动态活体引擎已部署
⚠️ *战区架构已重组,请务必点击下方指令并发送,以同步新的防撞档案:*
\`${REG_MSG}\`" >/dev/null 2>&1
echo -e "\033[32m✅ 升级通知已推送!请前往 TG 点击注册指令完成身份同步!\033[0m"
# [路由表 2]: 现代静默升级 (老版本 >= v3.3.2)
else
echo -e "\n📡 [路由枢纽] 正在执行静默平滑升级 (v${OLD_VERSION} -> v${TARGET_VERSION})..."
curl -s -X POST "${TG_API_URL}" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=✨ *IP-Sentinel 引擎热更新完成!*
📍 节点:\`${NODE_ALIAS}\`
🌐 IP\`${SAFE_PUBLIC_IP}\`
🚀 状态v${TARGET_VERSION} OTA 动态活体引擎已部署" >/dev/null 2>&1
echo -e "\033[32m✅ 升级成功通知已推送到您的 Telegram\033[0m"
fi
# [清理遗留垃圾并刷新版本号]
sed -i '/^NAME_HASHED=/d' "$CONFIG_FILE" 2>/dev/null # 抹除上个版本的临时基因锁
if grep -q "^AGENT_VERSION=" "$CONFIG_FILE"; then
sed -i "s/^AGENT_VERSION=.*/AGENT_VERSION=\"$TARGET_VERSION\"/" "$CONFIG_FILE"
else
echo "AGENT_VERSION=\"$TARGET_VERSION\"" >> "$CONFIG_FILE"
fi
echo -e "\n📡 正在向指挥部发送升级成功战报..."
curl -s -X POST "${TG_API_URL}" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=✨ *IP-Sentinel 引擎热更新完成!*
📍 节点:\`${NODE_NAME}\`
🌐 IP\`${BIND_IP}\`
🚀 状态v3.3.0 OTA 动态活体养护引擎已部署" >/dev/null 2>&1
echo -e "\033[32m✅ 升级成功通知已推送到您的 Telegram\033[0m"
else
# [全新安装路由]
echo -e "\n📡 正在向指挥部发送注册暗号..."
# 构造注册暗号 (V3.1.3 协议升级: 携带 REGION_CODE 大区标识)
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${BIND_IP}|${AGENT_PORT}"
# 执行主动推送
PUSH_RESULT=$(curl -s -X POST "${TG_API_URL}" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=✨ *IP-Sentinel 部署成功!*
📍 区域:${REGION_NAME}
🌐 IP${SAFE_PUBLIC_IP}
🌐 IP${BIND_IP}
🔌 端口:${AGENT_PORT}
🔑 *请点击下方指令复制并回复给机器人:*
@@ -701,8 +500,8 @@ if [[ -n "$TG_TOKEN" ]]; then
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active firewalld | grep -qw active; then
FW_MSG="firewall-cmd --zone=public --add-port=$AGENT_PORT/tcp --permanent && firewall-cmd --reload"
elif command -v iptables >/dev/null 2>&1; then
# 智能双栈雷达:根据对外公网 IP 属性,动态下发对应的防火墙放行指令
if [[ "$SAFE_PUBLIC_IP" == *":"* ]]; then
# 智能双栈雷达:根据绑定的 IP 属性,动态下发对应的防火墙放行指令
if [[ "$BIND_IP" == *":"* ]]; then
FW_MSG="ip6tables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
else
FW_MSG="iptables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: mod_google.sh (Google 业务逻辑模块 - 动态锚点版)
# 脚本名称: mod_google.sh (Google 业务逻辑模块)
# 核心功能: 执行坐标微抖动、模拟真实阅读时长、会话行为拉伸
# ==========================================================
@@ -16,15 +16,11 @@ else
exit 1
fi
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback (v3.4.0 引入版本探针)
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback
if ! type log >/dev/null 2>&1; then
log() {
# [v3.4.0 核心] 提取当前配置中的版本锚点
local local_ver="${AGENT_VERSION:-未知}"
mkdir -p "${INSTALL_DIR}/logs"
# 统一日志格式,注入 [版本号] 追踪标识
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [v%-5s] [%-5s] [%-7s] [%s] %s\n" "$local_ver" "$2" "$1" "$REGION_CODE" "$3" >> "${INSTALL_DIR}/logs/sentinel.log"
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$2" "$1" "$REGION_CODE" "$3" >> "${INSTALL_DIR}/logs/sentinel.log"
}
fi
@@ -52,8 +48,8 @@ get_random_coord() {
}
# --- [环境初始化] ---
# [v3.3.1修改] 优先读取对外公网面孔作为哈希种子,兼容 NAT 机的空 BIND_IP
CURRENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
# [v3.0.2修复] 直接读取系统已锁定的锚点 IP彻底杜绝“获取IP失败”及隧道偏移
CURRENT_IP="${BIND_IP:-Unknown}"
# -----------------------------------------------------------
# [V3.1.5] 哈希锚定法 (Hash-Seeded Persona)

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: mod_trust.sh (IP 信用净化模块 - 动态锚点版)
# 脚本名称: mod_trust.sh (IP 信用净化模块 V3.1.4 拓扑自适应版)
# 核心功能: 动态扫描本地 LBS 冷数据,提取权威白名单,执行流量净化
# ==========================================================
@@ -41,16 +41,12 @@ if [ ${#TRUST_URLS[@]} -eq 0 ]; then
TRUST_URLS=("https://en.wikipedia.org/wiki/Special:Random" "https://www.apple.com/" "https://www.microsoft.com/")
fi
# 3. 日志规范化 (v3.4.0 引入版本探针)
# 3. 日志规范化
log_msg() {
local TYPE=$1
local MSG=$2
local TIME=$(date "+%Y-%m-%d %H:%M:%S")
# [v3.4.0 核心] 提取当前配置中的版本锚点
local local_ver="${AGENT_VERSION:-未知}"
# 日志格式注入 [版本号] 追踪标识,保持对齐
echo "[$TIME] [v%-5s] [%-5s] [Trust ] [$REGION] $MSG" | sed "s/%-5s/$local_ver/;s/%-5s/$TYPE/" | tee -a "$LOG_FILE"
echo "[$TIME] [$TYPE] [Trust ] [$REGION] $MSG" | tee -a "$LOG_FILE"
}
# 4. 锁定单次会话指纹
@@ -63,8 +59,8 @@ if [ -f "$UA_FILE" ]; then
TOTAL_UA=${#UA_POOL[@]}
if [ "$TOTAL_UA" -gt 0 ]; then
# [v3.3.1修改] 优先使用固化的公网 IP 作为哈希种子,防止 NAT 节点指纹同质化
SEED=$(echo -n "${PUBLIC_IP:-${BIND_IP:-127.0.0.1}}" | cksum | awk '{print $1}')
# 以本地锁定的公网 IP (BIND_IP) 为种子计算 CRC32 哈希值
SEED=$(echo -n "${BIND_IP:-127.0.0.1}" | cksum | awk '{print $1}')
# 利用确定的种子,在全球 4000 的库中,计算出本机的 3 个绝对专属坐标
IDX1=$(( SEED % TOTAL_UA ))

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 - 动态锚点版)
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 V2.0 智能分配版)
# 核心功能: 防并发延迟启动、功能开关(Feature Flag)自适应、多模块概率轮盘调度
# ==========================================================
@@ -15,26 +15,14 @@ if [ ! -f "$CONFIG_FILE" ]; then
fi
source "$CONFIG_FILE"
# ================== [新增: 文件排他锁,防止并发重入引发内存雪崩] ==================
exec 200>"/tmp/ip_sentinel_runner.lock"
if ! flock -n 200; then
echo "[$(date)] ⚠️ 上一轮巡逻任务尚未结束,本次触发自动取消。" >> "$LOG_FILE"
exit 0
fi
# ==================================================================================
# 2. 全局日志写入函数 (导出给子进程共享使用v3.4.0 引入版本探针)
# 2. 全局日志写入函数 (导出给子进程共享使用)
log() {
local module=$1
local level=$2
local msg=$3
# [v3.4.0 核心] 提取当前配置中的版本锚点
local local_ver="${AGENT_VERSION:-未知}"
# 保证日志目录存在
mkdir -p "${INSTALL_DIR}/logs"
# 日志格式注入 [版本号] 追踪标识
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [v%-5s] [%-5s] [%-7s] [%s] %s\n" "$local_ver" "$level" "$module" "$REGION_CODE" "$msg" >> "$LOG_FILE"
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$level" "$module" "$REGION_CODE" "$msg" >> "$LOG_FILE"
}
export -f log
export CONFIG_FILE INSTALL_DIR

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# ==========================================================
# 脚本名称: tg_daemon.sh (Telegram 互动监听守护进程 - 动态锚点版)
# 核心功能: 极低功耗长轮询监听、节点溯源、版本继承
# 脚本名称: tg_daemon.sh (Telegram 互动监听守护进程)
# 核心功能: 极低功耗长轮询监听 TG 指令,实现远程控制
# ==========================================================
INSTALL_DIR="/opt/ip_sentinel"
@@ -16,11 +16,6 @@ source "$CONFIG_FILE"
# 如果没有配置 TG 机器人,则安静退出守护进程
[ -z "$TG_TOKEN" ] || [ -z "$CHAT_ID" ] && exit 0
# [核心: 动态版本锚点与防撞甲身份载入]
LOCAL_VER="${AGENT_VERSION:-未知}"
IP_HASH=$(echo "${PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
NODE_NAME="$(hostname | cut -c 1-10)-${IP_HASH}"
# 2. 初始化消息偏移量 (Offset) 记录文件,防止重启后重复处理老消息
OFFSET=0
[ -f "$OFFSET_FILE" ] && OFFSET=$(cat "$OFFSET_FILE")
@@ -49,20 +44,20 @@ while true; do
if [ "$MSG_CHAT_ID" == "$CHAT_ID" ]; then
case "$MSG_TEXT" in
"/run")
send_msg "🚀 **[${NODE_NAME}]** 正在后台触发 IP 养护任务 (v${LOCAL_VER})..."
send_msg "🚀 **[指令下达]** 正在后台立即触发 IP 养护任务..."
# 使用 nohup 另起后台独立进程运行,防止阻塞当前监听器的循环
nohup bash "${INSTALL_DIR}/core/mod_google.sh" >/dev/null 2>&1 &
;;
"/log")
LOG_DATA=$(tail -n 15 "${INSTALL_DIR}/logs/sentinel.log")
send_msg "📄 **[${NODE_NAME}] 实时日志 (v${LOCAL_VER}):**%0A\`\`\`log%0A${LOG_DATA}%0A\`\`\`"
send_msg "📄 **[最近 15 行系统日志]**%0A\`\`\`log%0A${LOG_DATA}%0A\`\`\`"
;;
"/report")
# 触发生成一次战报
bash "${INSTALL_DIR}/core/tg_report.sh"
;;
"/help"|"/start")
HELP_MSG="🛡️ **IP-Sentinel 边缘控制台**%0A📍 节点: \`${NODE_NAME}\`%0A🔖 版本: \`v${LOCAL_VER}\`%0A%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
HELP_MSG="🛡️ **IP-Sentinel 控制台**%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
send_msg "$HELP_MSG"
;;
esac

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# ==========================================================
# 脚本名称: tg_report.sh (Telegram 每日战报模块 - 动态锚点版)
# 核心功能: 适配 Feature Flag 架构,按需展示独立统计数据OTA 更新预警
# 脚本名称: tg_report.sh (Telegram 每日战报模块 V6.0 动态拼装版)
# 核心功能: 适配 Feature Flag 架构,按需展示 Google/Trust 独立统计数据
# ==========================================================
INSTALL_DIR="/opt/ip_sentinel"
@@ -19,12 +19,7 @@ if [ -z "$TG_TOKEN" ] || [ -z "$CHAT_ID" ]; then
fi
# 2. 节点元数据抓取 (v3.2.2 协议自适应与多级容灾版)
# [v3.5.2 核心: 引入双轨身份架构]
if [ -z "$NODE_NAME" ]; then
IP_HASH=$(echo "${PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
NODE_NAME="$(hostname | cut -c 1-10)-${IP_HASH}"
fi
NODE_ALIAS="${NODE_ALIAS:-$NODE_NAME}"
NODE_NAME=$(hostname | cut -c 1-15)
# --- [防线 1: 底层路由锁定与协议自适应] ---
CURL_BIND_OPT=""
@@ -41,8 +36,8 @@ fi
# 多节点容灾探测出口 IP (注入协议自适应)
CURRENT_IP=$( (curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -s -m 5 api.ip.sb/ip || curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -s -m 5 ifconfig.me) 2>/dev/null | tr -d '[:space:]' )
# [v3.3.1 修改] 强制兜底:如果外部 API 挂了,优先使用固化的对外公网面孔 (兼容 NAT 机的空 BIND_IP)
[ -z "$CURRENT_IP" ] && CURRENT_IP="${PUBLIC_IP:-$BIND_IP}"
# 强制兜底:如果所有外部 API 挂了,直接使用本地强行锁定的 BIND_IP
[ -z "$CURRENT_IP" ] && CURRENT_IP="$BIND_IP"
# 为可能获取到的 IPv6 自动添加方括号护甲
[[ "$CURRENT_IP" == *":"* ]] && [[ "$CURRENT_IP" != *"["* ]] && CURRENT_IP="[${CURRENT_IP}]"
@@ -89,14 +84,14 @@ case "$REGION_CODE" in
*) FLAG="🌐" ;;
esac
# 3. 截取过去 24 小时的日志 (每天48次轮询保留最新 1000 行足以覆盖单日战报)
LOG_CONTENT=$(tail -n 1000 "$LOG_FILE" 2>/dev/null)
# 3. 截取过去 24 小时的日志
LOG_CONTENT=$(find "$LOG_FILE" -mtime -1 -exec cat {} \; 2>/dev/null)
if [ -z "$LOG_CONTENT" ]; then
read -r -d '' MSG <<EOT
🛑 **[IP-Sentinel] 告警:节点异常**
----------------------------
📍 **节点名称**: \`${NODE_ALIAS}\`
📍 **节点名称**: \`${NODE_NAME}\`
⚠️ **警告**: 过去 24 小时无运行日志!
🛠️ **建议**: 节点可能刚部署完毕,请在面板手动执行一次养护动作。
EOT
@@ -114,7 +109,7 @@ else
# 开始组装战报头部
MSG="📊 **IP-Sentinel 每日简报 (${FLAG} ${REGION_NAME})**
----------------------------
📍 **节点名称**: \`${NODE_ALIAS}\`
📍 **节点名称**: \`${NODE_NAME}\`
📡 **出口 IP**: \`${CURRENT_IP}\`
🛡️ **IP 属性**: ${IP_TYPE}"
@@ -158,39 +153,10 @@ else
🕒 **最近执行快照 [${LAST_MOD:-"System"}]:**
时间: ${LAST_TIME:-"暂无数据"}
结论: ${LAST_SCORE:-"暂无数据"}"
fi
# ==========================================
# 5. [核心: OTA 云端版本探针与告警模块]
# ==========================================
# 从配置文件提取当前本地版本,若无则默认为未知
LOCAL_VER="${AGENT_VERSION:-未知}"
# 极轻量级探针: 抓取 GitHub 云端的 version.txt (超时 3 秒KV解析法)
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
REMOTE_VER=$(curl -s -m 3 "${REPO_RAW_URL}/version.txt" | grep "^AGENT_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
# 构建底部引擎状态块
MSG="$MSG
结论: ${LAST_SCORE:-"暂无数据"}
----------------------------
🛡️ **系统引擎状态**
当前运行版本: \`v${LOCAL_VER}\`"
💡 哨兵正在后台默默守护您的资产。"
# 比对逻辑:如果成功抓到了远端版本,且和本地不一样
if [ -n "$REMOTE_VER" ] && [ "$REMOTE_VER" != "$LOCAL_VER" ]; then
MSG="$MSG
最新官方版本: \`v${REMOTE_VER}\` (✨有新版)
💡 *司令部提示:检测到新版装甲,请长官登录节点执行平滑热更新!*"
elif [ -n "$REMOTE_VER" ] && [ "$REMOTE_VER" == "$LOCAL_VER" ]; then
MSG="$MSG
最新官方版本: \`v${REMOTE_VER}\` (✅已是最新)
💡 *哨兵正在后台默默守护您的资产。*"
else
# 抓取失败兜底
MSG="$MSG
💡 *哨兵正在后台默默守护您的资产。*"
fi
# 5. 调用 API 推送 (接入安全网关)

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 - 动态锚点版)
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 V3.1.4 焦土版)
# 核心功能: 无痕清理守护进程、定时任务、运行目录及临时缓存
# ==========================================================
@@ -9,13 +9,6 @@ INSTALL_DIR="/opt/ip_sentinel"
echo "========================================================"
echo " 🗑️ 准备卸载 IP-Sentinel (边缘节点 Edge Agent)"
# [核心: 动态读取并播报即将销毁的本地版本号]
CONFIG_FILE="${INSTALL_DIR}/config.conf"
if [ -f "$CONFIG_FILE" ]; then
CURRENT_VER=$(grep "^AGENT_VERSION=" "$CONFIG_FILE" | cut -d'"' -f2)
[ -n "$CURRENT_VER" ] && echo " 📍 目标版本: v${CURRENT_VER}"
fi
echo "========================================================"
# 1. 停止运行中的守护进程与主控模块 (涵盖所有历史版本进程)
@@ -24,8 +17,6 @@ echo "[1/3] 正在终止后台守护进程与所有养护任务..."
# 使用 pkill 替代传统的 pgrep | xargs指令更短、容错率更高
pkill -9 -f "tg_daemon.sh" >/dev/null 2>&1
pkill -9 -f "agent_daemon.sh" >/dev/null 2>&1
# [v3.4.0 优化] 确保清理所有 python3 调起的 Webhook 实例
pkill -9 -f "python3.*webhook.py" >/dev/null 2>&1
pkill -9 -f "webhook.py" >/dev/null 2>&1
pkill -9 -f "runner.sh" >/dev/null 2>&1
pkill -9 -f "updater.sh" >/dev/null 2>&1

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# ==========================================================
# 脚本名称: updater.sh (IP-Sentinel 养料注入与分频调度中枢 - 动态锚点版)
# 核心功能: 静默更新热数据/LBS、指纹库错峰调度、强制出站死锁、版本无缝继承
# 脚本名称: updater.sh (IP-Sentinel V3.3.0 养料注入与分频调度中枢)
# 核心功能: 静默更新热搜词/LBS、指纹库错峰调度、强制出站死锁
# ==========================================================
INSTALL_DIR="/opt/ip_sentinel"
@@ -18,14 +18,10 @@ if [ ! -f "$CONFIG_FILE" ]; then
fi
source "$CONFIG_FILE"
# 2. 全局日志写入函数 (v3.4.0 引入版本探针)
# 2. 全局日志写入函数
log() {
# [v3.4.0 核心] 提取当前配置中的版本锚点
local local_ver="${AGENT_VERSION:-未知}"
mkdir -p "${INSTALL_DIR}/logs"
# 日志格式注入 [版本号] 追踪标识
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [v%-5s] [%-5s] [%-7s] [%s] %s\n" "$local_ver" "$2" "$1" "$REGION_CODE" "$3" >> "$LOG_FILE"
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$2" "$1" "$REGION_CODE" "$3" >> "$LOG_FILE"
}
log "Updater" "INFO " "========== 触发后台静默 OTA 热数据更新 =========="

View File

@@ -1,45 +0,0 @@
syria
leylah fernandez
highway hotline sk
real betis vs braga
vincent bolloré
europa conference league
resident alien
battlefield 6
aston villa vs bologna
nottm forest vs porto
soccer
lamour est dans le pré
luis suárez
listeria
strc
bayern
arda güler
aleksandar pavlović
kooora
yalla kora
ina garten
jordan goodwin
jerami grant
jimmy snuggerud
clav
bts
zach galifianakis
billy crystal
club américa vs nashville
allison williams
CBC News
Canada.ca
Toronto Weather
NHL Scores
Amazon.ca
Air Canada
Service Canada
CRA login
Rogers
Bell Internet
Tim Hortons
Indigo
Canadian Tire
Walmart Canada
Toronto Raptors

View File

@@ -1,63 +1,3 @@
rayo vallecano
harry meghan
irland
betis sevilla
vermisste person
konferenz league
garda
judith hoersch
jörg pilawa
strasbourg
real madrid vs bayern
kicker
mbappe
flashscore
sport1
vini jr
bet365
kompany
jude bellingham
upamecano
gute zeiten, schlechte zeiten
inflation
fog warning
wetter bochum
wetter aachen
wetter bonn
onet
protest
jamie dornan
heizöl
champions league
uefa champions league
keytruda
péter magyar
psg
hechingen
şampiyonlar ligi
tschernobyl 1986
amazon video
paris saint-germain
dietrich grönemeyer
fränkische schweiz
scarlett johansson
jeff bezos
dan brown
паспорт громадянина україни для виїзду за кордон
serena williams
kampf der realitystars
манчестер юнайтед лидс
catherine deneuve
bobzin
sprit
kev
abschiebung
steuer
masters rory mcilroy
großglockner
news38
jessie cave
michael schulte
wetter frankfurt heute
bundesliga ergebnisse
aktuelle nachrichten deutschland

View File

@@ -1,45 +0,0 @@
armengol
pau víctor
braga fc
fiorentina vs crystal palace
morante hoy
iago aspas
aston villa
real betis vs braga
ministerio de sanidad
az - shakhtar
arsenal
tiktok
harry kane
sudan
lunin
airef
tiempo de juego
fc bayern
militao
bellingham
supervivientes
jalen green
rockstar games
bam adebayo
china
suns - trail blazers
euromillones
ldu quito - mirassol
davion mitchell
universitario - coquimbo unido
El País
Marca
RTVE Noticias
La Liga
Real Madrid
Barcelona FC
Tiempo Madrid
Renfe
Iberia
Amazon España
El Corte Inglés
Hacienda
Mercadona
YouTube Música
Entradas Cine

View File

@@ -1,62 +1,3 @@
programme télé
tchernobyl
géraldine maillet
biot
racing
liga europa
tv ce soir
programme tv de ce soir
brad pitt
aston villa
michael olise
robert ménard
match ce soir
sporting
ester exposito
bellingham
iptv
militao
jeff goldblum
lunin
kiev
julien royal
viktor orbán
aqababe
nhl
suns trail blazers
bam adebayo
davion mitchell
l
santos recoleta football club
atletico madrid
tf1
uefa champions league
camille cerf
giorgi mamardashvili
streaming football
atlético madryt fc barcelona
miss france
rts
leonardo balerdi
yann barthes
alain delon
loto du 13 avril 2026
juan arbeláez
hbo
katy perry justin trudeau
jacob elordi
tondela gil vicente
le rugbynistère
epstein
kino
horoscope du 13 avril 2026
golf masters augusta 2026
boursorama bourse
cac 40
sept à huit
ligne 12 métro
alice taglioni
pedro sánchez
meteo paris
actualités en direct
résultats ligue 1

View File

@@ -1,62 +1,3 @@
歐聯
神戶勝利船
潘宏彬
姚正菁
木乃伊
ios 26
李克寧木乃伊
江美儀
田啟文
曼寧加
arsenal
ucl
歐洲聯賽冠軍盃
arsenal vs sporting
bayern vs real madrid
real madrid
皇馬
拜仁慕尼黑 對 皇家馬德里
claude
補貼
nba 直播
航空公司
向華強
李嘉欣
typhoon
nba
nba線上看
nba直播
全民國家安全教育日
運輸署
liverpool vs psg
利物浦
barcelona
歐冠
馬德里競技 對 巴塞隆納
利物浦 對 巴黎聖日耳曼
hkjc
馬會
航空
2035
man united vs leeds
曼聯 對 里茲聯
prediction market
預測市場
polymarket
巴基斯坦
sndk
楊何蓓茵
樂珈嘉
姜濤
日經平均指數
飲茶
上市公司
daniel caesar
中年好聲音4
香港天文台
煤氣
livenation
政府
香港天文台天氣預報
MTR 港鐵路線圖
OpenRice 附近美食

View File

@@ -1,64 +1,7 @@
小芝風花
中井亜美
afc u20女子アジアカップ
ネオジオ
uefaヨーロッパリーグ
加藤史帆
町田ゼルビア
志田未来
伊藤英明
島田麻央
al-nassr vs al-ettifaq
arsenal
レアル・マドリード
バイエルン
abema
real madrid
lucknow super giants vs royal challengers bengaluru standings
bayern vs real madrid
給付
wowow
小泉進次郎
政権
ミキティ
ソニック
今日のドジャースの結果
新名神高速道路
わたせせいぞう
ピーチ航空
山本由伸
アレックス・ベシア
リバプール
champions league
エルニーニョ
アトレティコ 対 バルセロナ
オープンワールド
atlético madrid vs barcelona
松田好花
リコール
man united vs leeds
白鵬翔
日本アカデミー賞 最優秀助演男優賞
マンu 対 リーズ u
サンディスク 株価
らじるらじる
マクドナルド
ロシア
広島市
ゲイブル・スティーブソン
日本維新の会
新 日本 繊維
高見沢 俊彦
不登校
後期高齢者医療制度
バーミヤン
宮澤エマ
チケプラ
横綱
宮里美香
東京 天気 明日
新宿 おすすめ 居酒屋
最新のニュース 速報
ゴールド 相場 チャート
近くの静かなカフェ
円安 影響 生活
地震 速報
円安 影響 生活

View File

@@ -1,45 +0,0 @@
real betis
sean connery
l1 nieuws
ronaldinho
demi de boer
bondgenoten
frank masmeijer
real betis - braga
ethereum
aston villa - bologna
manuel neuer
neuer
olise
mbappe
sporting
live tv
bayern munchen
arda güler
ziggo
arda guler
netflix
frenkie de jong
kanye west
vandaag inside
at5
veroordeling
verenigde staten
alec baldwin
anna paulowna
şampiyonlar ligi
NOS Nieuws
Buienradar
Rijksoverheid
Albert Heijn
Funda
Marktplaats
KLM
Ziggo
ING Bank
Eredivisie
Amsterdam Weer
Bol.com
Treinkaartjes NS
PostNL
Pathé

View File

@@ -1,63 +1,3 @@
aston villa vs bologna
mumbai indians vs punjab kings standings
al sadd vs vissel kobe
amd share price
opus 4.7
pete hegseth
naman dhir
yen singapore dollar
mayank rawat
dji pocket 4
real madrid
al-nassr vs al-ettifaq
bayern vs real madrid
arsenal vs sporting
lucknow super giants vs royal challengers bengaluru standings
is claude down
claude
allbirds
red sea
rcb vs lsg
retirement
asia flights delays cancellations
suns vs trail blazers
johnny somali
bam adebayo
zhang linghe pursuit of jade
roman gofman
cruz azul vs lafc
ocbc
santos vs recoleta
atlético madrid vs barcelona
ipl schedule
liverpool vs psg
iran blockade strait of hormuz
kartik tyagi
carlos alcaraz
propertylimbrothers
byeon woo-seok
mahathir mohamad
csk vs kkr
man united vs leeds
cbse class 10 result 2026 date
euphoria season 3
srh vs rr
tamil new year 2026
low de wei
pope
flexar
microsoft outlook
new rolex 2026
medical classification
blasphemy law
big bang coachella 2026
小贩
malaysia fuel price crisis
sbti personality test
cancer survivor
tim cook
spurs vs nuggets
asia flights cancelled delayed
singapore weather forecast
mrt map singapore
straitstimes breaking news

View File

@@ -1,25 +0,0 @@
歐聯
菡生婦幼診所
台鐵訂票
飛機
東光路
货币
amd
rklb
航空母艦
axti
Yahoo奇摩
天氣
蝦皮購物
PChome
Momo購物網
Mobile01
Dcard
巴哈姆特
中時電子報
聯合新聞網
台灣高鐵
台鐵時刻表
中華電信
統一發票
勞動部

View File

@@ -1,63 +1,3 @@
europa conference league
bromley fc
paul merton
chris wood
istanbul
turkey
lucy watson
thiago silva
bednarek
jan bednarek
vincent kompany
mbappe
luis suarez sporting
madrid fc
andriy lunin
what did bec say to rachel mafs
yalla kora
geovany quenda
sporting cp
pavlovic
talktalk
arne slot drops mohamed salah
suns vs trail blazers
italian
used cars
mlb
roman
johnny somali
windows update
davion mitchell
hbo max
bolton wanderers
barca vs atletico
kemi badenoch
warren zaïre-emery
barca
samuel west
barcelona fc
lamine yamal
hbomax
noah okafor
casemiro
talksport
lazio
leeds united fixtures
bruno fernandes
afc champions league
meteor
carlos queiroz
travel warning
tori amos
cloud
reading
rolls-royce smr
istanbul airport
a27
bridget phillipson
tottenham standings
may bank holiday 2026
toto wolff
london weather today
bbc news latest
premier league fixtures

View File

@@ -1,61 +1,3 @@
giancarlo stanton
real betis
prosecution of daniel duggan
liv morgan
mikey williams
indiana fever sophie cunningham baptism
gregory donnell morgan jr
why are the sirens going off
leylah fernandez
strasbourg vs mainz
michael olise
ريال مدريد
dazn
paramount
univision
jude bellingham
sam antonacci
real madrid
bayern
arda güler
los angeles dodgers
vandenberg launch schedule
ryan dunn
alex vesia
ken jennings
ucla baseball
padres standings
mets vs dodgers match player stats
bo bichette
jorge polanco
psg
barca
vix
fcb
barcelona schedule
tarjeta roja
a knight of the seven kingdoms season 2
charlotte flair
usa network
natalie sago
carlos queiroz
carlos batista
katie boulter
levante - getafe
levante vs getafe
mcilroy green jacket presentation
man united vs leeds
7-eleven closing locations
cloud
sports
sony playstation
alaska airline
toronto
sydney
paris
tokyo
delhi
sykkuno drama
Los Angeles weather today
S&P 500 stock chart
local coffee shops near me
@@ -65,6 +7,3 @@ AI startups in Silicon ValleySan Jose weather this weekend
Silicon Valley tech news
best tacos in San Jose
Apple park visitor center hours
Seattle Weather
Las Vegas strip
Charlotte Hornets

View File

@@ -1,45 +0,0 @@
porto vs
crystal palace
porto
uefa europa conference
betis đấu với braga
real betis vs braga
aston villa đấu với bologna
fiorentina đấu với crystal palace
c2
cup c2
thể thao
arda güler
aleksandar pavlović
ars
90
real
real madrid
xoi
luong sơn
fpt
phan văn giang
nhà ở xã hội
club america
giàu
đỗ mỹ linh
sun group
hưng yên
nvl
américa đấu với nashville
neymar
VnExpress
Zing News
Thời tiết Hà Nội
Giá vàng hôm nay
Shopee VN
Tiki
Vietjet Air
Vietnam Airlines
Bóng đá trực tuyến
Lịch thi đấu Euro
Xổ số miền Bắc
Grab Vietnam
VTV Go
Học tiếng Anh
Du lịch Đà Lạt

View File

@@ -1,183 +1,105 @@
{
"version": "3.5.2",
"updated_at": "2026-04-16",
"continents": [
"version": "3.1.0",
"updated_at": "2026-04-11",
"countries": [
{
"id": "ASIA",
"name": "亚太战区 (Asia-Pacific)",
"countries": [
{
"id": "JP",
"name": "Japan (日本)",
"keyword_file": "kw_JP.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Tokyo", "name": "Tokyo (东京)" } ] }
]
},
{
"id": "SG",
"name": "Singapore (新加坡)",
"keyword_file": "kw_SG.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Singapore", "name": "Singapore (新加坡)" } ] }
]
},
{
"id": "HK",
"name": "Hong Kong (香港)",
"keyword_file": "kw_HK.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "HongKong", "name": "Hong Kong (香港)" } ] }
]
},
{
"id": "VN",
"name": "Vietnam (越南)",
"keyword_file": "kw_VN.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Hanoi", "name": "Hanoi (河内)" } ] }
]
},
{
"id": "TW",
"name": "Taiwan (台湾)",
"keyword_file": "kw_TW.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Taipei", "name": "Taipei (台北)" } ] }
]
}
]
},
{
"id": "EUROPE",
"name": "欧洲战区 (Europe)",
"countries": [
{
"id": "UK",
"name": "United Kingdom (英国)",
"keyword_file": "kw_UK.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "London", "name": "London (伦敦)" },
{ "id": "Coventry", "name": "Coventry (考文垂)" }
]
}
]
},
{
"id": "DE",
"name": "Germany (德国)",
"keyword_file": "kw_DE.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Frankfurt", "name": "Frankfurt (法兰克福)" } ] }
]
},
{
"id": "FR",
"name": "France (法国)",
"keyword_file": "kw_FR.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Paris", "name": "Paris (巴黎)" } ] }
]
},
{
"id": "NL",
"name": "Netherlands (荷兰)",
"keyword_file": "kw_NL.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Amsterdam", "name": "Amsterdam (阿姆斯特丹)" } ] }
]
},
{
"id": "ES",
"name": "Spain (西班牙)",
"keyword_file": "kw_ES.txt",
"states": [
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Madrid", "name": "Madrid (马德里)" } ] }
]
}
]
},
{
"id": "AMERICAS",
"name": "美洲战区 (Americas)",
"countries": [
{
"id": "US",
"name": "United States (美国)",
"keyword_file": "kw_US.txt",
"states": [
{
"id": "CA",
"name": "California (加州)",
"cities": [
{ "id": "Los_Angeles", "name": "Los Angeles (洛杉矶)" },
{ "id": "San_Jose", "name": "San Jose (圣何塞)" }
]
},
{
"id": "IL",
"name": "Illinois (伊利诺伊州)",
"cities": [
{ "id": "Warrenville", "name": "Warrenville (沃伦维尔)" }
]
},
{
"id": "NC",
"name": "North Carolina (北卡罗来纳州)",
"cities": [
{ "id": "Charlotte", "name": "Charlotte (夏洛特)" }
]
},
{
"id": "NV",
"name": "Nevada (内华达州)",
"cities": [
{ "id": "Las_Vegas", "name": "Las Vegas (拉斯维加斯)" }
]
},
{
"id": "OR",
"name": "Oregon (俄勒冈州)",
"cities": [
{ "id": "Bend", "name": "Bend (本德)" }
]
},
{
"id": "UT",
"name": "Utah (犹他州)",
"cities": [
{ "id": "Salt_Lake_City", "name": "Salt Lake City (盐湖城)" }
]
},
{
"id": "WA",
"name": "Washington (华盛顿州)",
"cities": [
{ "id": "Seattle", "name": "Seattle (西雅图)" }
]
}
]
},
"id": "US",
"name": "United States (美国)",
"keyword_file": "kw_US.txt",
"states": [
{
"id": "CA",
"name": "Canada (加拿大)",
"keyword_file": "kw_CA.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "Toronto", "name": "Toronto (多伦多)" },
{ "id": "Montreal", "name": "Montreal (蒙特利尔)" }
]
}
"name": "California (加)",
"cities": [
{ "id": "Los_Angeles", "name": "Los Angeles (洛杉矶)" },
{ "id": "San_Jose", "name": "San Jose (圣何塞)" }
]
}
]
},
{
"id": "JP",
"name": "Japan (日本)",
"keyword_file": "kw_JP.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "Tokyo", "name": "Tokyo (东京)" }
]
}
]
},
{
"id": "UK",
"name": "United Kingdom (英国)",
"keyword_file": "kw_UK.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "London", "name": "London (伦敦)" }
]
}
]
},
{
"id": "DE",
"name": "Germany (德国)",
"keyword_file": "kw_DE.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "Frankfurt", "name": "Frankfurt (法兰克福)" }
]
}
]
},
{
"id": "FR",
"name": "France (法国)",
"keyword_file": "kw_FR.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "Paris", "name": "Paris (巴黎)" }
]
}
]
},
{
"id": "SG",
"name": "Singapore (新加坡)",
"keyword_file": "kw_SG.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "Singapore", "name": "Singapore (新加坡)" }
]
}
]
},
{
"id": "HK",
"name": "Hong Kong (香港)",
"keyword_file": "kw_HK.txt",
"states": [
{
"id": "Default",
"name": "Default State",
"cities": [
{ "id": "HongKong", "name": "Hong Kong (香港)" }
]
}
]
}
]
}
}

View File

@@ -1,17 +0,0 @@
{
"region_name": "Canada - Montreal",
"google_module": {
"base_lat": 45.5017,
"base_lon": -73.5673,
"lang_params": "hl=en&gl=CA",
"valid_url_suffix": "ca"
},
"trust_module": {
"white_urls": [
"https://en.wikipedia.org/wiki/Special:Random",
"https://www.cbc.ca/",
"https://www.amazon.ca/",
"https://www.theweathernetwork.com/ca"
]
}
}

View File

@@ -1,20 +0,0 @@
{
"region_name": "Canada - Toronto",
"google_module": {
"base_lat": 43.6532,
"base_lon": -79.3832,
"lang_params": "hl=en&gl=CA",
"valid_url_suffix": "ca"
},
"trust_module": {
"white_urls": [
"https://en.wikipedia.org/wiki/Special:Random",
"https://www.canada.ca/en.html",
"https://www.cbc.ca/",
"https://www.thestar.com/",
"https://www.ctvnews.ca/",
"https://www.canadapost-postescanada.ca/",
"https://www.td.com/"
]
}
}

View File

@@ -1,20 +0,0 @@
{
"region_name": "Spain - Madrid",
"google_module": {
"base_lat": 40.4168,
"base_lon": -3.7038,
"lang_params": "hl=es&gl=ES",
"valid_url_suffix": "es"
},
"trust_module": {
"white_urls": [
"https://es.wikipedia.org/wiki/Especial:Aleatoria",
"https://www.elmundo.es/",
"https://www.elpais.com/",
"https://www.marca.com/",
"https://www.rtve.es/",
"https://www.zara.com/es/",
"https://www.elcorteingles.es/"
]
}
}

View File

@@ -1,20 +0,0 @@
{
"region_name": "Netherlands - Amsterdam",
"google_module": {
"base_lat": 52.3676,
"base_lon": 4.9041,
"lang_params": "hl=nl&gl=NL",
"valid_url_suffix": "nl"
},
"trust_module": {
"white_urls": [
"https://nl.wikipedia.org/wiki/Speciaal:Willekeurig",
"https://www.rijksoverheid.nl/",
"https://www.nos.nl/",
"https://www.bol.com/",
"https://www.nu.nl/",
"https://www.buienradar.nl/",
"https://www.telegraaf.nl/"
]
}
}

View File

@@ -1,20 +0,0 @@
{
"region_name": "Taiwan - Taipei",
"google_module": {
"base_lat": 25.0330,
"base_lon": 121.5654,
"lang_params": "hl=zh-TW&gl=TW",
"valid_url_suffix": "com.tw"
},
"trust_module": {
"white_urls": [
"https://zh.wikipedia.org/wiki/Special:Random",
"https://tw.yahoo.com/",
"https://www.pchome.com.tw/",
"https://www.momoshop.com.tw/",
"https://www.ruten.com.tw/",
"https://www.mobile01.com/",
"https://www.dcard.tw/"
]
}
}

View File

@@ -1,17 +0,0 @@
{
"region_name": "United Kingdom - Coventry",
"google_module": {
"base_lat": 52.4068,
"base_lon": -1.5197,
"lang_params": "hl=en&gl=GB",
"valid_url_suffix": "co.uk"
},
"trust_module": {
"white_urls": [
"https://en.wikipedia.org/wiki/Special:Random",
"https://www.bbc.co.uk/",
"https://www.amazon.co.uk/",
"https://www.theguardian.com/uk"
]
}
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Warrenville",
"google_module": { "base_lat": 41.8164, "base_lon": -88.1748, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Charlotte",
"google_module": { "base_lat": 35.2271, "base_lon": -80.8431, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Las Vegas",
"google_module": { "base_lat": 36.1699, "base_lon": -115.1398, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Bend",
"google_module": { "base_lat": 44.0582, "base_lon": -121.3153, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Salt Lake City",
"google_module": { "base_lat": 40.7608, "base_lon": -111.8910, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,5 +0,0 @@
{
"region_name": "United States - Seattle",
"google_module": { "base_lat": 47.6062, "base_lon": -122.3321, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
}

View File

@@ -1,20 +0,0 @@
{
"region_name": "Vietnam - Hanoi",
"google_module": {
"base_lat": 21.0285,
"base_lon": 105.8542,
"lang_params": "hl=vi&gl=VN",
"valid_url_suffix": "vn"
},
"trust_module": {
"white_urls": [
"https://vi.wikipedia.org/wiki/Đặc_biệt:Ngẫu_nhiên",
"https://chinhphu.vn/",
"https://vnexpress.net/",
"https://tuoitre.vn/",
"https://vtv.vn/",
"https://shopee.vn/",
"https://tiki.vn/"
]
}
}

View File

@@ -1,28 +1,21 @@
#!/bin/bash
# ==========================================================
# 脚本名称: install_master.sh (IP-Sentinel 控制中枢部署脚本 - 动态锚点版)
# 脚本名称: install_master.sh (IP-Sentinel 控制中枢部署脚本 v3.2.3)
# 核心功能: 部署/卸载调度中枢、SQLite 资产管理、平滑热更新引擎
# ==========================================================
# 你的 GitHub 仓库 Raw 数据直链前缀
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
# [新增] 提取仓库直链前缀变量,方便后续在官方库和私库间一键切换
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy"
# 临时改为私库地址用于测试
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
# [核心: 动态提取 Master 专属版本锚点 (KV 解析法)]
# 通过 grep 定位 MASTER_VERSION 行,再通过 cut 提取等号右侧的值
TARGET_VERSION=$(curl -s -m 3 "${REPO_RAW_URL}/version.txt" | grep "^MASTER_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
# 🛡️ 兜底防线:如果网络波动拉取失败,启用内置的安全兜底版本
TARGET_VERSION=${TARGET_VERSION:-"3.5.0"}
MASTER_DIR="/opt/ip_sentinel_master"
DB_FILE="${MASTER_DIR}/sentinel.db"
echo "========================================================"
# [修改] 将欢迎语改为更通用的文案,因为现在不仅能部署,还能卸载
echo " 🧠 欢迎使用 IP-Sentinel Master (控制中枢) v${TARGET_VERSION}"
echo " 🧠 欢迎使用 IP-Sentinel Master (控制中枢) v3.2.2"
echo "========================================================"
# [新增] 交互式操作菜单:支持选择部署或调用卸载程序
@@ -31,9 +24,6 @@ echo " 1) 🚀 部署 Master 控制中枢"
echo " 2) 🗑️ 一键卸载 Master 中枢"
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定被误删档
ACTION_CHOICE=${ACTION_CHOICE:-1}
if [ "$ACTION_CHOICE" == "2" ]; then
echo -e "\n⏳ 正在拉取卸载程序..."
# [新增逻辑] 使用上面定义的 REPO_RAW_URL 动态拉取卸载脚本,执行后自动销毁临时文件
@@ -60,15 +50,7 @@ if [ "$ACTION_CHOICE" == "1" ] && [ -f "${MASTER_DIR}/master.conf" ]; then
# 汲取原配置进入内存
source "${MASTER_DIR}/master.conf"
# [v3.4.0 核心] 升级后立即同步/补录版本号至配置文件
if grep -q "^MASTER_VERSION=" "${MASTER_DIR}/master.conf"; then
sed -i "s/^MASTER_VERSION=.*/MASTER_VERSION=\"$TARGET_VERSION\"/" "${MASTER_DIR}/master.conf"
else
echo "MASTER_VERSION=\"$TARGET_VERSION\"" >> "${MASTER_DIR}/master.conf"
fi
echo -e "\033[32m✅ 已激活 [平滑升级模式],版本已锚定为 v${TARGET_VERSION}...\033[0m"
echo -e "\033[32m✅ 已激活 [平滑升级模式],即将跳过基础配置,直接更新核心中枢...\033[0m"
else
echo -e "\033[33m🔄 您选择了重新配置,旧的中枢数据将被彻底抹除。\033[0m"
fi
@@ -115,8 +97,6 @@ if [ "$UPGRADE_MODE" == "false" ]; then
read -p "请输入 Telegram Bot Token: " TG_TOKEN
cat > "${MASTER_DIR}/master.conf" << EOF
# IP-Sentinel Master 本地固化配置 (v${TARGET_VERSION})
MASTER_VERSION="$TARGET_VERSION"
TG_TOKEN="$TG_TOKEN"
DB_FILE="$DB_FILE"
MASTER_DIR="$MASTER_DIR"
@@ -124,7 +104,7 @@ EOF
fi
# 🛑 拦截块结束
# 3. 初始化 SQLite 数据库 (幂等操作,升级模式下由 tg_master.sh 负责热修补)
# 3. 初始化 SQLite 数据库 (幂等操作,升级模式下可安全修补表结构)
echo -e "\n[3/4] 正在初始化 SQLite 数据库表结构..."
sqlite3 "$DB_FILE" <<EOF
CREATE TABLE IF NOT EXISTS nodes (
@@ -133,10 +113,6 @@ CREATE TABLE IF NOT EXISTS nodes (
agent_ip TEXT,
agent_port TEXT,
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
region TEXT DEFAULT 'UNKNOWN',
node_alias TEXT,
enable_google TEXT DEFAULT 'true',
enable_trust TEXT DEFAULT 'true',
PRIMARY KEY(chat_id, node_name)
);
EOF

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# ==========================================================
# 脚本名称: tg_master.sh (Master 端调度枢纽 - 动态锚点版)
# 脚本名称: tg_master.sh (Master 端调度枢纽 V3.0.4 动态签名版)
# 核心功能: 监听 TG、操作 SQLite、Webhook 精准调度、403权限拦截、僵尸节点清理
# ==========================================================
@@ -9,12 +9,6 @@ CONF="/opt/ip_sentinel_master/master.conf"
[ ! -f "$CONF" ] && exit 1
source "$CONF"
# [核心: 运行态版本继承与云通信地址]
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
# MASTER_VERSION 已经在上方的 source "$CONF" 中被载入
# 如果本地极度陈旧没有该变量,才给定一个基础兜底值,避免变量为空导致崩溃
MASTER_VERSION=${MASTER_VERSION:-"3.5.0"}
OFFSET_FILE="${MASTER_DIR}/.tg_offset"
[[ -f $OFFSET_FILE ]] || echo "0" > $OFFSET_FILE
@@ -36,13 +30,6 @@ edit_msg() {
-d "chat_id=$1" -d "message_id=$2" -d "text=$3" -d "parse_mode=Markdown" > /dev/null
}
# [v3.5.3 新增: 支持内联键盘的原位 UI 重绘函数]
edit_ui() {
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/editMessageText" \
-H "Content-Type: application/json" \
-d "{\"chat_id\":\"$1\",\"message_id\":\"$2\",\"text\":\"$3\",\"parse_mode\":\"Markdown\",\"reply_markup\":{\"inline_keyboard\":$4}}" > /dev/null
}
# 数据库执行函数
db_exec() {
sqlite3 "$DB_FILE" "$1"
@@ -67,12 +54,9 @@ generate_signed_url() {
}
# ========================================================================
# ================== [v3.1.3/v3.5.3 核心: 数据库结构无损热升级] ==================
# 自动探测并增加缺失字段,屏蔽已存在的报错,保护老节点数据
# ================== [v3.1.3 核心: 数据库结构无损热升级] ==================
# 自动探测并增加 region 字段,屏蔽已存在的报错,保护老节点数据
db_exec "ALTER TABLE nodes ADD COLUMN region TEXT DEFAULT 'UNKNOWN';" 2>/dev/null
db_exec "ALTER TABLE nodes ADD COLUMN node_alias TEXT;" 2>/dev/null
db_exec "ALTER TABLE nodes ADD COLUMN enable_google TEXT DEFAULT 'true';" 2>/dev/null
db_exec "ALTER TABLE nodes ADD COLUMN enable_trust TEXT DEFAULT 'true';" 2>/dev/null
# ========================================================================
# --- 核心轮询循环 ---
@@ -89,23 +73,6 @@ while true; do
CHAT_ID=$(echo "$UPDATE" | jq -r '.message.chat.id // .callback_query.message.chat.id')
TEXT=$(echo "$UPDATE" | jq -r '.message.text // .callback_query.data')
REPLY_TO_TEXT=$(echo "$UPDATE" | jq -r '.message.reply_to_message.text // empty')
# ================== [v3.5.2 新增: 拦截别名修改的对话回复] ==================
if [[ "$REPLY_TO_TEXT" == *"✏️ 请回复本消息以重命名节点:"* ]]; then
# 精准提取被回复消息中的节点主键名
TARGET_NODE=$(echo "$REPLY_TO_TEXT" | grep -v "✏️" | grep -v "仅限" | tr -d '\` ' | tr -cd 'a-zA-Z0-9_.-' | head -n 1)
# [v3.5.2 热修复] 废除 Bash 原生 tr 命令的中文白名单 (不支持 Unicode 会误删中文)。
# 改用黑名单策略:仅自动转化下划线,剔除引号、特殊符号和冒号(防止破坏内部路由)
# 将完整的中文原样送入 Base64 编码,最终严格正则清洗交由 Agent 的 Python 引擎处理!
NEW_ALIAS=$(echo "$TEXT" | sed 's/_/-/g' | tr -d '"'\''\`\$\|&;<>\n\r:' | cut -c 1-30)
if [ -n "$TARGET_NODE" ] && [ -n "$NEW_ALIAS" ]; then
# 强行重写内部路由
TEXT="do_rename:${TARGET_NODE}:${NEW_ALIAS}"
fi
fi
# ================== [v3.0.1 新增: 消除转圈圈与获取消息ID] ==================
CB_ID=$(echo "$UPDATE" | jq -r '.callback_query.id // empty')
@@ -122,27 +89,21 @@ while true; do
if [[ "$TEXT" == *"#REGISTER#"* ]]; then
REG_LINE=$(echo "$TEXT" | grep "#REGISTER#" | head -n 1 | tr -d '\` ')
# V3.5.2 兼容性拆包: 支持 6字段(双轨)、5字段(单轨)、4字段(远古)
# V3.1.3 兼容性拆包: 判断是新版协议 (5个字段) 还是老版协议 (4个字段)
FIELD_COUNT=$(echo "$REG_LINE" | awk -F'|' '{print NF}')
if [ "$FIELD_COUNT" -ge 6 ]; then
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT RAW_ALIAS <<< "$REG_LINE"
elif [ "$FIELD_COUNT" -eq 5 ]; then
if [ "$FIELD_COUNT" -ge 5 ]; then
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
RAW_ALIAS="$RAW_NODE"
else
IFS='|' read -r MAGIC RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
RAW_REGION="UNKNOWN"
RAW_ALIAS="$RAW_NODE"
fi
# 🛡️ 强制字符白名单过滤:保留历史特征不变
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
AGENT_REGION=$(echo "$RAW_REGION" | tr -cd 'a-zA-Z0-9' | cut -c 1-10)
AGENT_REGION=$(echo "$RAW_REGION" | tr -cd 'a-zA-Z0-9' | cut -c 1-10) # 提取国家大区
NODE_NAME=$(echo "$RAW_NODE" | tr -cd 'a-zA-Z0-9_.-' | cut -c 1-30)
AGENT_IP=$(echo "$RAW_IP" | tr -cd 'a-zA-Z0-9.:\[\]-' | cut -c 1-50)
AGENT_PORT=$(echo "$RAW_PORT" | tr -cd '0-9' | cut -c 1-5)
NODE_ALIAS=$(echo "$RAW_ALIAS" | tr -d '"'\''\`\$\|&;<>\n\r' | cut -c 1-30)
[ -z "$NODE_ALIAS" ] && NODE_ALIAS="$NODE_NAME"
if [[ "$AGENT_IP" =~ ^127\.|^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^::1$|^localhost$ ]]; then
send_msg "$CHAT_ID" "⛔ **安全拦截**:禁止注册内网或回环 IP防止 SSRF 攻击渗透。"
@@ -154,9 +115,9 @@ while true; do
continue
fi
# [核心] 入库时追加 node_alias 字段
db_exec "INSERT INTO nodes (chat_id, node_name, agent_ip, agent_port, last_seen, region, node_alias) VALUES ('$CHAT_ID', '$NODE_NAME', '$AGENT_IP', '$AGENT_PORT', CURRENT_TIMESTAMP, '$AGENT_REGION', '$NODE_ALIAS') ON CONFLICT(chat_id, node_name) DO UPDATE SET agent_ip='$AGENT_IP', agent_port='$AGENT_PORT', last_seen=CURRENT_TIMESTAMP, region='$AGENT_REGION', node_alias='$NODE_ALIAS';"
send_msg "$CHAT_ID" "**司令部确认 (v${MASTER_VERSION})**%0A节点 \`${NODE_ALIAS}\` 档案已录入!"
# 入库时追加 region 字段
db_exec "INSERT INTO nodes (chat_id, node_name, agent_ip, agent_port, last_seen, region) VALUES ('$CHAT_ID', '$NODE_NAME', '$AGENT_IP', '$AGENT_PORT', CURRENT_TIMESTAMP, '$AGENT_REGION') ON CONFLICT(chat_id, node_name) DO UPDATE SET agent_ip='$AGENT_IP', agent_port='$AGENT_PORT', last_seen=CURRENT_TIMESTAMP, region='$AGENT_REGION';"
send_msg "$CHAT_ID" "✅ 司令部确认!节点接入成功: \`$NODE_NAME\` ($AGENT_IP:$AGENT_PORT)"
# ================== [v3.1.3 丝滑连招: 直接呼出全球大区雷达] ==================
REGION_DATA=$(db_exec "SELECT region, COUNT(*) FROM nodes WHERE chat_id='$CHAT_ID' GROUP BY region;")
@@ -168,7 +129,6 @@ while true; do
case "$REGION_NAME" in
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
"SG") FLAG="🇸🇬" ;; "UK"|"GB") FLAG="🇬🇧" ;; "DE") FLAG="🇩🇪" ;; "FR") FLAG="🇫🇷" ;;
"CA") FLAG="🇨🇦" ;; "AU") FLAG="🇦🇺" ;; "KR") FLAG="🇰🇷" ;; "NL") FLAG="🇳🇱" ;; "BR") FLAG="🇧🇷" ;; "IN") FLAG="🇮🇳" ;; "TW") FLAG="🇹🇼" ;;
esac
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
done <<< "$REGION_DATA"
@@ -185,16 +145,8 @@ while true; do
# ==========================================
case "$TEXT" in
"/start"|"/menu")
# [核心: 抓取云端最新 Master 版本 (KV 解析法)]
REMOTE_VER=$(curl -s -m 2 "${REPO_RAW_URL}/version.txt" | grep "^MASTER_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
VER_INFO="当前版本: \`v${MASTER_VERSION}\`"
if [ -n "$REMOTE_VER" ] && [ "$REMOTE_VER" != "$MASTER_VERSION" ]; then
VER_INFO="${VER_INFO}\n✨ **发现新版本**: \`v${REMOTE_VER}\` (请尽快更新主控)"
fi
BTNS="[[{\"text\":\"🖥️ 我的节点列表\",\"callback_data\":\"list_nodes\"}], [{\"text\":\"🚀 全节点日报汇总\",\"callback_data\":\"all_reports\"}], [{\"text\":\"🛠️ 全节点一键维护\",\"callback_data\":\"all_run\"}]]"
send_ui "$CHAT_ID" "🛡️ **IP-Sentinel 司令部**\n${VER_INFO}\n\n欢迎回来,长官。请下达指令:" "$BTNS"
send_ui "$CHAT_ID" "🛡️ **IP-Sentinel 司令部**\n欢迎回来长官。请下达指令" "$BTNS"
;;
"all_reports")
@@ -204,9 +156,9 @@ while true; do
else
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在召唤所有哨兵回传简报...**"
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
# 🛡️ [v3.0.4] 动态签名防重放批量下发
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_report")
curl -s -m 5 "$TARGET_URL" > /dev/null &
sleep 0.2 # [新增] 流量削峰:每秒最多并发下发 5 个,保护 Master 网络栈
done
fi
;;
@@ -219,9 +171,9 @@ while true; do
else
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在唤醒所有哨兵执行系统维护...**"
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
# 🛡️ [v3.0.4] 动态签名防重放批量下发 (维护模块)
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_run")
curl -s -m 5 "$TARGET_URL" > /dev/null &
sleep 0.2 # [新增] 流量削峰:防止瞬间 fork 导致句柄耗尽
done
fi
;;
@@ -238,9 +190,8 @@ while true; do
[ -z "$REGION_NAME" ] && REGION_NAME="UNKNOWN"
FLAG="🌐"
case "$REGION_NAME" in
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
"SG") FLAG="🇸🇬" ;; "UK"|"GB") FLAG="🇬🇧" ;; "DE") FLAG="🇩🇪" ;; "FR") FLAG="🇫🇷" ;;
"CA") FLAG="🇨🇦" ;; "AU") FLAG="🇦🇺" ;; "KR") FLAG="🇰🇷" ;; "NL") FLAG="🇳🇱" ;; "BR") FLAG="🇧🇷" ;; "IN") FLAG="🇮🇳" ;; "TW") FLAG="🇹🇼" ;;
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
"SG") FLAG="🇸🇬" ;; "UK"|"GB") FLAG="🇬🇧" ;; "DE") FLAG="🇩🇪" ;; "FR") FLAG="🇫🇷" ;;
esac
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
done <<< "$REGION_DATA"
@@ -254,17 +205,15 @@ while true; do
TARGET_REGION=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9')
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
# [v3.5.2] 提取物理主键和展示别名
NODE_LIST=$(db_exec "SELECT node_name, IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND region='$TARGET_REGION';")
NODE_LIST=$(db_exec "SELECT node_name FROM nodes WHERE chat_id='$CHAT_ID' AND region='$TARGET_REGION';")
if [ -z "$NODE_LIST" ]; then
send_msg "$CHAT_ID" "⚠️ 该战区下暂无可用节点。"
else
BTNS="["
COL=0
ROW_STR="["
while IFS='|' read -r N_NAME N_ALIAS; do
[ -z "$N_NAME" ] && continue
ROW_STR="$ROW_STR{\"text\":\"🖥️ $N_ALIAS\",\"callback_data\":\"manage:$N_NAME\"},"
for N in $NODE_LIST; do
ROW_STR="$ROW_STR{\"text\":\"🖥️ $N\",\"callback_data\":\"manage:$N\"},"
COL=$((COL+1))
if [ $COL -eq 2 ]; then
ROW_STR="${ROW_STR%,}]"
@@ -272,7 +221,7 @@ while true; do
COL=0
ROW_STR="["
fi
done <<< "$NODE_LIST"
done
# 如果是奇数,补齐最后的尾巴
if [ $COL -eq 1 ]; then
ROW_STR="${ROW_STR%,}]"
@@ -285,76 +234,11 @@ while true; do
;;
manage:*)
# 🛡️ 强制过滤节点名,防止面板渲染时发生 XSS 或注入
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
TARGET_ALIAS=$(db_exec "SELECT IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
[ -z "$TARGET_ALIAS" ] && TARGET_ALIAS="$TARGET_NODE"
# [v3.5.3] 将改名收纳至 L5 高级面板,替换为“高级控制功能”
BTNS="[[{\"text\":\"📍 Google 纠偏\",\"callback_data\":\"google:$TARGET_NODE\"}, {\"text\":\"🛡️ 信用净化\",\"callback_data\":\"trust:$TARGET_NODE\"}], [{\"text\":\"📜 实时日志\",\"callback_data\":\"log:$TARGET_NODE\"}, {\"text\":\"📊 统计战报\",\"callback_data\":\"report:$TARGET_NODE\"}], [{\"text\":\"⚙️ 高级控制功能\",\"callback_data\":\"adv:$TARGET_NODE\"}, {\"text\":\"🗑️ 剔除失联节点\",\"callback_data\":\"del:$TARGET_NODE\"}], [{\"text\":\"⬅️ 返回大区目录\",\"callback_data\":\"list_nodes\"}]]"
if [ -n "$MSG_ID" ]; then
edit_ui "$CHAT_ID" "$MSG_ID" "⚙️ **目标锁定**: \`$TARGET_ALIAS\`\n*(身份标识: $TARGET_NODE)*\n请选择战术动作" "$BTNS"
else
send_ui "$CHAT_ID" "⚙️ **目标锁定**: \`$TARGET_ALIAS\`\n*(身份标识: $TARGET_NODE)*\n请选择战术动作" "$BTNS"
fi
;;
adv:*)
# [L5 高级控制面板渲染]
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
TARGET_ALIAS=$(db_exec "SELECT IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
[ -z "$TARGET_ALIAS" ] && TARGET_ALIAS="$TARGET_NODE"
# 抓取当前节点的开关状态
TOGGLE_INFO=$(db_exec "SELECT enable_google, enable_trust FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
ST_GOOGLE=$(echo "$TOGGLE_INFO" | cut -d'|' -f1)
ST_TRUST=$(echo "$TOGGLE_INFO" | cut -d'|' -f2)
# 动态渲染状态机红绿灯 UI
[ "$ST_GOOGLE" == "true" ] && BTN_G="🔴 停用 Google 纠偏" && ACT_G="false" || { BTN_G="🟢 启用 Google 纠偏"; ACT_G="true"; }
[ "$ST_TRUST" == "true" ] && BTN_T="🔴 停用信用净化" && ACT_T="false" || { BTN_T="🟢 启用信用净化"; ACT_T="true"; }
BTNS="[[{\"text\":\"$BTN_G\",\"callback_data\":\"toggle:google:$TARGET_NODE:$ACT_G\"}], [{\"text\":\"$BTN_T\",\"callback_data\":\"toggle:trust:$TARGET_NODE:$ACT_T\"}], [{\"text\":\"✏️ 修改节点备注\",\"callback_data\":\"rename:$TARGET_NODE\"}], [{\"text\":\"⬅️ 返回节点面板\",\"callback_data\":\"manage:$TARGET_NODE\"}]]"
if [ -n "$MSG_ID" ]; then
edit_ui "$CHAT_ID" "$MSG_ID" "⚙️ **高级控制** | \`$TARGET_ALIAS\`\n请下达控制指令" "$BTNS"
else
send_ui "$CHAT_ID" "⚙️ **高级控制** | \`$TARGET_ALIAS\`\n请下达控制指令" "$BTNS"
fi
;;
toggle:*)
# [动态启停通信闭环]
IFS=':' read -r CMD MOD_NAME TARGET_NODE TARGET_STATE <<< "$TEXT"
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
AGENT_INFO=$(db_exec "SELECT agent_ip, agent_port FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
AGENT_IP=$(echo "$AGENT_INFO" | cut -d'|' -f1)
AGENT_PORT=$(echo "$AGENT_INFO" | cut -d'|' -f2)
if [ -n "$AGENT_IP" ] && [ -n "$AGENT_PORT" ]; then
TARGET_URL=$(generate_signed_url "$AGENT_IP" "$AGENT_PORT" "/trigger_toggle")
TARGET_URL="${TARGET_URL}&mod=${MOD_NAME}&state=${TARGET_STATE}"
RESPONSE=$(curl -s -m 5 "$TARGET_URL" || echo "FAILED")
if [[ "$RESPONSE" == *"Action Accepted"* ]]; then
# 下发成功,更新 DB原位重绘
db_exec "UPDATE nodes SET enable_${MOD_NAME}='$TARGET_STATE' WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE';"
TOGGLE_INFO=$(db_exec "SELECT enable_google, enable_trust FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
ST_GOOGLE=$(echo "$TOGGLE_INFO" | cut -d'|' -f1)
ST_TRUST=$(echo "$TOGGLE_INFO" | cut -d'|' -f2)
[ "$ST_GOOGLE" == "true" ] && BTN_G="🔴 停用 Google 纠偏" && ACT_G="false" || { BTN_G="🟢 启用 Google 纠偏"; ACT_G="true"; }
[ "$ST_TRUST" == "true" ] && BTN_T="🔴 停用信用净化" && ACT_T="false" || { BTN_T="🟢 启用信用净化"; ACT_T="true"; }
BTNS="[[{\"text\":\"$BTN_G\",\"callback_data\":\"toggle:google:$TARGET_NODE:$ACT_G\"}], [{\"text\":\"$BTN_T\",\"callback_data\":\"toggle:trust:$TARGET_NODE:$ACT_T\"}], [{\"text\":\"✏️ 修改节点备注\",\"callback_data\":\"rename:$TARGET_NODE\"}], [{\"text\":\"⬅️ 返回节点面板\",\"callback_data\":\"manage:$TARGET_NODE\"}]]"
TARGET_ALIAS=$(db_exec "SELECT IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
edit_ui "$CHAT_ID" "$MSG_ID" "⚙️ **高级控制** | \`$TARGET_ALIAS\`\n✅ 成功:模块 [$MOD_NAME] 状态已切换为 $TARGET_STATE" "$BTNS"
else
send_msg "$CHAT_ID" "❌ 指令下发失败,节点可能离线或未更新至 v3.5.3。"
fi
fi
# 【核心升级】拆分下发按钮,精准对应 Google 与 Trust 两个模块,并排版为 3 行 2 列
BTNS="[[{\"text\":\"📍 Google 纠偏\",\"callback_data\":\"google:$TARGET_NODE\"}, {\"text\":\"🛡️ 信用净化\",\"callback_data\":\"trust:$TARGET_NODE\"}], [{\"text\":\"📜 实时日志\",\"callback_data\":\"log:$TARGET_NODE\"}, {\"text\":\"📊 统计战报\",\"callback_data\":\"report:$TARGET_NODE\"}], [{\"text\":\"🗑️ 剔除失联节点\",\"callback_data\":\"del:$TARGET_NODE\"}, {\"text\":\"⬅️ 返回大区目录\",\"callback_data\":\"list_nodes\"}]]"
send_ui "$CHAT_ID" "⚙️ **目标锁定**: \`$TARGET_NODE\`\n请选择战术动作" "$BTNS"
;;
del:*)
@@ -385,50 +269,6 @@ while true; do
fi
;;
rename:*)
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
# [v3.5.2] 发送 ForceReply 引导用户回复
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "{\"chat_id\":\"$CHAT_ID\",\"text\":\"✏️ 请回复本消息以重命名节点:\n\`$TARGET_NODE\`\n(仅限中英文、数字最长20字符)\",\"parse_mode\":\"Markdown\",\"reply_markup\":{\"force_reply\":true}}" > /dev/null
;;
do_rename:*)
# [v3.5.2] 内部重命名路由 (已被第2处的代码拦截并格式化)
IFS=':' read -r CMD TARGET_NODE NEW_ALIAS <<< "$TEXT"
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
AGENT_INFO=$(db_exec "SELECT agent_ip, agent_port FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
AGENT_IP=$(echo "$AGENT_INFO" | cut -d'|' -f1)
AGENT_PORT=$(echo "$AGENT_INFO" | cut -d'|' -f2)
if [ -n "$AGENT_IP" ] && [ -n "$AGENT_PORT" ]; then
send_msg "$CHAT_ID" "⏳ 正在向 \`$TARGET_NODE\` 下发重命名指令,正在建立加密隧道..."
TARGET_URL=$(generate_signed_url "$AGENT_IP" "$AGENT_PORT" "/trigger_rename")
# [绝密防线: Base64 编码绕过一切传输限制与 WAF 拦截]
ALIAS_B64=$(echo -n "$NEW_ALIAS" | base64 | tr -d '\n' | tr '+/' '-_')
TARGET_URL="${TARGET_URL}&b64=${ALIAS_B64}"
RESPONSE=$(curl -s -m 5 "$TARGET_URL" || echo "FAILED")
if [ "$RESPONSE" == "FAILED" ]; then
send_msg "$CHAT_ID" "❌ 指令下发超时!请检查节点连通性。"
elif [[ "$RESPONSE" == *"Action Accepted"* ]]; then
# [v3.5.2 极致丝滑] 确认 Agent 修改成功后Master 立即自动同步本地 SQLite 数据库!
db_exec "UPDATE nodes SET node_alias='$NEW_ALIAS' WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE';"
send_msg "$CHAT_ID" "✅ 通讯成功!节点别名已下发: \`$NEW_ALIAS\`%0A*(司令部档案已自动刷新,雷达面板已同步)*"
else
# 增加输出 RESPONSE 调试信息,排查任何拦截死因
send_msg "$CHAT_ID" "⚠️ 节点拒绝了请求,请确保 Agent 已更新至 v3.5.2%0A(回传信息: \`${RESPONSE}\`)"
fi
else
send_msg "$CHAT_ID" "❌ 数据库中未找到该节点的通讯地址。"
fi
;;
# 【核心升级】增加拦截规则,支持 google 和 trust 前缀
google:*|trust:*|run:*|report:*|log:*)
# 🛡️ 提取并强制过滤动作参数、节点名与 CHAT_ID

View File

@@ -1,21 +1,14 @@
#!/bin/bash
# ==========================================================
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本 - 动态锚点版)
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本)
# 核心功能: 终止调度进程、清理看门狗定时任务、抹除数据库与配置
# ==========================================================
MASTER_DIR="/opt/ip_sentinel_master"
CONF_FILE="${MASTER_DIR}/master.conf"
echo "========================================================"
echo " 🗑️ 准备卸载 IP-Sentinel Master (控制中枢)"
# [v3.4.0 优化] 卸载前读取并播报中枢版本号
if [ -f "$CONF_FILE" ]; then
MASTER_VER=$(grep "^MASTER_VERSION=" "$CONF_FILE" | cut -d'"' -f2)
[ -n "$MASTER_VER" ] && echo " 📍 目标版本: v${MASTER_VER}"
fi
echo "========================================================"
echo -e "\n⚠ 警告: 此操作将永久删除包含所有节点档案的 SQLite 数据库!"
@@ -27,8 +20,7 @@ fi
# 1. 停止运行中的 Master 守护进程
echo "[1/3] 正在终止后台中枢调度进程..."
# [优化] 使用 pkill 替代 pgrep | xargs指令更短、容错率更高
pkill -9 -f "tg_master.sh" >/dev/null 2>&1 || true
pgrep -f tg_master.sh | xargs -r kill -9 >/dev/null 2>&1
# 2. 清除看门狗定时任务 (Cron)
echo "[2/3] 正在清理系统定时任务 (Cron)..."

View File

@@ -1,83 +0,0 @@
import urllib.request
import xml.etree.ElementTree as ET
import os
import json
import re
# ================== [路径防弹装甲] ==================
# 无论在哪里执行该脚本,都能精准反推项目根目录
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
MAP_JSON_PATH = os.path.join(PROJECT_ROOT, "data", "map.json")
DATA_DIR = os.path.join(PROJECT_ROOT, "data", "keywords")
# ====================================================
# 特殊战区代码映射 (Google Trends RSS 要求)
GEO_FIX = {'UK': 'GB'}
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
def get_active_regions():
"""动态提取 map.json 中的战区 (适配 v3.5.0 大洲战区降维架构)"""
try:
with open(MAP_JSON_PATH, 'r', encoding='utf-8') as f:
data = json.load(f)
regions = []
# 第一层穿透:遍历所有大洲战区 (continents)
for continent in data.get('continents', []):
# 第二层穿透:遍历大洲下的所有国家 (countries)
for country in continent.get('countries', []):
if 'id' in country:
regions.append(country['id'])
return regions
except Exception as e:
print(f"❌ [读取地图失败]: {e}")
return []
def fetch_trends(region_code):
"""从 Google Trends 抓取当日热搜"""
geo = GEO_FIX.get(region_code, region_code)
url = f"https://trends.google.com/trending/rss?geo={geo}"
try:
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=10) as response:
xml_data = response.read()
root = ET.fromstring(xml_data)
return [re.sub(r'[\n\r\t]', ' ', item.find('title').text).strip()
for item in root.findall('./channel/item')
if item.find('title') is not None]
except Exception as e:
print(f"⚠️ {region_code} 抓取异常: {e}")
return []
def update_file(region, new_words):
"""滑动窗口更新,保留 200 条最热记录"""
os.makedirs(DATA_DIR, exist_ok=True)
file_path = os.path.join(DATA_DIR, f"kw_{region}.txt")
old_words = []
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
old_words = [l.strip() for l in f if l.strip()]
# 新词排在最前面,去重
combined = new_words + [w for w in old_words if w not in new_words]
final_list = combined[:200]
with open(file_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(final_list) + '\n')
print(f"✅ [同步完成] {region}: 注入 {len(new_words)} 条新热点")
if __name__ == '__main__':
regions = get_active_regions()
if not regions:
print("🛑 未发现活跃战区,请检查 map.json")
exit(1)
for r in regions:
print(f"📡 正在拉取 {r} 战区情报...")
words = fetch_trends(r)
if words:
update_file(r, words)

View File

@@ -1,2 +0,0 @@
AGENT_VERSION=3.5.4
MASTER_VERSION=3.5.4