mirror of
https://github.com/hotyue/IP-Sentinel.git
synced 2026-05-10 17:52:41 +08:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
539de62eee | ||
|
|
d514106e65 | ||
|
|
7f9c8a4dea | ||
|
|
31014e571d | ||
|
|
f5aa68a8dc | ||
|
|
63bbbd549e | ||
|
|
5e40ed426b | ||
|
|
aebf3a9e90 | ||
|
|
8a3d7c305b | ||
|
|
2d580eaea2 | ||
|
|
d7ab695372 | ||
|
|
0c250dfd17 | ||
|
|
52b12c7057 | ||
|
|
2166753569 | ||
|
|
46e418dfd0 | ||
|
|
673e0ce3e6 | ||
|
|
6203249782 | ||
|
|
05e708ed21 | ||
|
|
c4faa102cf | ||
|
|
4a1b32278c | ||
|
|
584ead387e | ||
|
|
efdc62ba68 | ||
|
|
67618587e4 | ||
|
|
a2c045a841 | ||
|
|
9b35c87cb4 | ||
|
|
e6c6e66e4b | ||
|
|
f1d36a2148 | ||
|
|
41906d0570 | ||
|
|
dc7d1c0f40 | ||
|
|
9768bed637 | ||
|
|
fa202a0405 | ||
|
|
b8bcd09134 | ||
|
|
ef8dc033cc | ||
|
|
03a54bcca0 | ||
|
|
f4f93d8955 | ||
|
|
07cac792f0 | ||
|
|
50edad9e25 | ||
|
|
69edf12620 | ||
|
|
0b448e2b7e | ||
|
|
d7b95136dd | ||
|
|
80d74111c4 | ||
|
|
223d7c4b56 | ||
|
|
de3b4ca6d3 | ||
|
|
423a765e5f | ||
|
|
ca74c31b96 | ||
|
|
9366240d62 | ||
|
|
1fa2cd10b1 | ||
|
|
3210c8cfcc | ||
|
|
d7e1e8fc8f | ||
|
|
a81f0564f1 | ||
|
|
3b20630e9e | ||
|
|
7321b76bb5 | ||
|
|
8016b0531c | ||
|
|
c7c93ea22e | ||
|
|
9aef79831c | ||
|
|
43f2e98459 | ||
|
|
5627c0115a | ||
|
|
f252b26088 | ||
|
|
12e6619ab9 | ||
|
|
a0bad1acf0 | ||
|
|
01d65972fd | ||
|
|
b1334fc06a | ||
|
|
918c73b5dc | ||
|
|
28f04a4eb9 | ||
|
|
26328e66c4 | ||
|
|
1d85837e79 | ||
|
|
ba8e2f1625 | ||
|
|
6b809138e5 | ||
|
|
6f4e871c7c | ||
|
|
fe2c9de80b | ||
|
|
6c0a589395 | ||
|
|
42a128fd6b | ||
|
|
a891f2017a | ||
|
|
66fdfb1908 | ||
|
|
aa2874fdcd | ||
|
|
120dd264c2 | ||
|
|
455f98fafd | ||
|
|
2c1041ebed | ||
|
|
0af3ff5cd8 | ||
|
|
6faa7b2c2a | ||
|
|
873b6996ca | ||
|
|
84832395bd | ||
|
|
96a7400be8 | ||
|
|
ba565978c6 | ||
|
|
b53032cc92 | ||
|
|
e96eacd6f8 | ||
|
|
4cac51673a | ||
|
|
be75e5b65c | ||
|
|
c7ece6620c | ||
|
|
7e9da4b82a | ||
|
|
c657c92b27 | ||
|
|
d43163703e | ||
|
|
1150450718 | ||
|
|
df6483afa8 | ||
|
|
29de2eadf8 | ||
|
|
318689f163 | ||
|
|
6b9563b858 | ||
|
|
2c7491449c | ||
|
|
f698bc4b92 | ||
|
|
c18a10dbd5 | ||
|
|
69e7803c40 | ||
|
|
b2a5afe562 | ||
|
|
45fda5f498 | ||
|
|
1459d5efec | ||
|
|
b7375e5e7d | ||
|
|
4a7f88a0da | ||
|
|
a0ec759dd7 |
44
.github/workflows/daily_keywords.yml
vendored
Normal file
44
.github/workflows/daily_keywords.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
46
README.md
46
README.md
@@ -10,17 +10,19 @@
|
|||||||
|
|
||||||
专为解决 VPS IP 被 Google 等数据库错误定位到中国大陆/香港(俗称“送中”)等问题而生。IP-Sentinel 已从单机脚本全面跃升为 **Master-Agent 分布式架构**。它像影子一样潜伏在全球各地的服务器后台,通过高度拟真的真实用户行为为你默默积累 IP 权重,并允许你通过 Telegram 随时随地对整个舰队进行毫秒级“点名”与“遥控”。
|
专为解决 VPS IP 被 Google 等数据库错误定位到中国大陆/香港(俗称“送中”)等问题而生。IP-Sentinel 已从单机脚本全面跃升为 **Master-Agent 分布式架构**。它像影子一样潜伏在全球各地的服务器后台,通过高度拟真的真实用户行为为你默默积累 IP 权重,并允许你通过 Telegram 随时随地对整个舰队进行毫秒级“点名”与“遥控”。
|
||||||
|
|
||||||
## ✨ 核心极客特性
|
## ✨ 核心极客特性 (Core Architecture)
|
||||||
|
|
||||||
* 🗺️ **全球拓扑矩阵 (Global Nexus)**:**v3.1 跨洲际跃升**。守护版图现已横跨亚、欧、美三大洲(**美、日、英、德、法、新、港**)。为每个国家注入极其硬核的“原生本地化”搜索词库与本土高权重站点(如政府、权威媒体、高铁网),真正实现“拟真融入”。
|
- 🎛️ **L5 降维中枢与动态状态机 (Microservices Console)**:引入四级战区降维解析与双轨身份制。全 Inline Keyboard 原位重绘交互,支持在 TG 面板一键下发毫秒级模块热启停 (Toggle)、丝滑改名与日志抓取,底层数据库实时刷新,彻底告别刷屏烦恼。
|
||||||
* ☁️ **云端中枢 (Public Master)**:引入官方公共机器人 [@OmniBeacon_bot](https://t.me/OmniBeacon_bot),新手无需部署 Master 司令部,部署 Agent 时一键回车即可调用官方加密网关,30 秒极速入伍!
|
- 🔄 **SSOT 溯源与热更新装甲 (Smooth Upgrade Engine)**:全系脚本彻底消灭硬编码,部署时动态抓取云端版本信标。搭载状态机嗅探逻辑与 OTA 预警探针,一键回车瞬间完成配置继承、数据同步与无损换代。
|
||||||
* 🧠 **分布式中枢 (Master-Agent)**:对于硬核极客,支持私有化部署。一台 Master 主控集成 SQLite 数据库,统管无数台 Agent 边缘节点,确保数据绝对私有。
|
- 🗺️ **全球拓扑矩阵与活体词库 (Global Nexus)**:守护版图横跨亚欧美三大洲。接入 GitHub Actions 云端流水线,每日静默同步全球各大区当日 Google 真实热搜榜单与高权重本土站点,让伪装行为永远贴合当地网络脉搏。
|
||||||
* 🔒 **叹息之墙 (Zero-Trust HMAC)**:**v3.1 核心重构**。全面废弃明文 Token,底层通讯引入 `时间戳 + HMAC-SHA256` 军用级动态签名。指令有效期仅 60 秒(阅后即焚),彻底免疫中间人抓包、重放攻击与端口爆破。
|
- 👻 **资产持久化与错峰调度 (Hash-Seeded Persona)**:摒弃随机抽取指纹,基于节点物理 IP 哈希永久锁定 3 个绝对专属设备,完美构建高权重真实家庭内网画像。叠加按需智能分频与随机防并发休眠,化解“惊群效应”。
|
||||||
* 🛡️ **工业级并发与自净引擎**:底层 Webhook 采用多线程模型彻底免疫慢速耗尽攻击;独创“智能清道夫”逻辑,覆盖安装/升级时自动绞杀僵尸进程与冗余定时任务,绝对纯净,告别玄学冲突。
|
- 🖧 **底层路由死锁与高精度探针 (Hard-Bind Routing)**:底层探测引擎强力接管 curl 核心参数 (`--interface`),将发出的每一滴伪装流量死死绑定在物理网卡或隧道 IP 上。配合多级 ISP 容灾链路,彻底杜绝双栈环境下的流量溢出与 API 误判。
|
||||||
* 🎮 **TG 战术面板 (Command Center)**:无需记忆繁琐命令,全 Inline Keyboard 交互。支持一键下发伪装指令、一键索要精准战报、**毫秒级抓取边缘节点实时运行日志**。
|
|
||||||
* 👻 **高仿真人类行为 (Human-Like)**:摒弃死板的 Ping/Curl,引入单次会话指纹锁定、10 米级 GPS 坐标微抖动、以及 60~150 秒的真实阅读停顿拉伸,完美避开 AI 封控。
|
**—— 💎 骨干基建特征 ——**
|
||||||
* 👁️🗨️ **玻璃房透明遥测 (Glasshouse Telemetry)**:**v3.1.2 全新上线**。引入基于 Cloudflare Workers 的全透明计数中枢,首页动态徽章实时展示全球真实装机与调用量。**绝对零隐私收集**,仅作原子累加,底层网关源码全开源,接受全网极客审计。
|
- 🏭 **自动化指纹兵工厂**:依托 GitHub Actions CI/CD 流水线,每月 1 日无人值守锻造 4000+ 带绝对物理分区的真实终端设备数据。
|
||||||
* ⚡ **丝滑战术交互 (Seamless UI)**:司令部交互面板像素级打磨。新节点发送暗号入伍成功后,司令部将**无缝零延迟自动呼出**最新的活跃节点阵列面板,彻底免除重复输入命令的繁琐,掌控感拉满。
|
- 🔒 **叹息之墙 (Zero-Trust HMAC)**:底层通讯引入 时间戳 + HMAC-SHA256 军用级动态签名。指令有效期仅 60 秒(阅后即焚),未授权请求直接触发系统级 403 物理熔断,彻底免疫中间人抓包与重放攻击。
|
||||||
|
- ☁️ **云端中枢 (Public Master)**:官方公共机器人 @OmniBeacon_bot,新手免自建,一键接入极速入伍!同时支持硬核极客私有化 SQLite 分布式部署。
|
||||||
|
- 👁️🗨️ **玻璃房透明遥测 (Glasshouse)**:基于 Cloudflare Workers 的全透明计数中枢,绝对零隐私收集,仅作原子累加,底层网关源码全开源。
|
||||||
|
|
||||||
## 📂 项目架构 (Monorepo)
|
## 📂 项目架构 (Monorepo)
|
||||||
|
|
||||||
@@ -28,19 +30,22 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
📦 IP-Sentinel
|
📦 IP-Sentinel
|
||||||
|
┣ 📂 .github/workflows/ # 🏭 自动化兵工厂:每月定时触发指纹生成的 CI/CD 流水线
|
||||||
┣ 📂 master/ # 🧠 司令部:SQLite 存储、TG 监听与 Webhook 调度中心
|
┣ 📂 master/ # 🧠 司令部:SQLite 存储、TG 监听与 Webhook 调度中心
|
||||||
┣ 📂 core/ # 🛡️ 边缘哨兵:Webhook 被动监听、高拟真养护引擎
|
┣ 📂 core/ # 🛡️ 边缘哨兵:Webhook 被动监听、哈希锚定执行引擎
|
||||||
|
┣ 📂 scripts/ # 🐍 兵工厂引擎:基于 Python 的多物理分区 UA 生成器
|
||||||
┣ 📂 data/ # 🗂️ 全球数据规则库 (动态拓扑)
|
┣ 📂 data/ # 🗂️ 全球数据规则库 (动态拓扑)
|
||||||
┃ ┣ 📜 map.json # 🌐 全球区域索引大脑 (Master Index)
|
┃ ┣ 📜 map.json # 🌍 全球区域大脑 (v3.5.0 大洲战区拓扑)
|
||||||
┃ ┣ 📂 regions/ # 🧊 冷数据:按 [国家/省州/城市] 深度细分的 LBS 锚点
|
┃ ┣ 📂 regions/ # 🧊 冷数据:按 [国家/省州/城市] 深度细分的 LBS 锚点
|
||||||
┃ ┣ 📂 keywords/ # 🔥 热数据:按国家归类的动态搜索词库 (OTA 自动更新)
|
┃ ┣ 📂 keywords/ # 🔥 热数据:按国家归类的动态搜索词库 (OTA 自动更新)
|
||||||
┃ ┗ 📜 user_agents.txt # 🔥 热数据:全局真实设备指纹池
|
┃ ┗ 📜 user_agents.txt # 🔥 热数据:由兵工厂每月锻造的绝对坐标专属设备库
|
||||||
|
┣ 📜 version.txt # 🚩 双端版本信标:Agent/Master 独立解耦的 KV 环境配置 (v3.5.1)
|
||||||
┗ 📂 telemetry/ # 👁️🗨️ 玻璃房计划:Cloudflare Workers 透明计数器网关源码
|
┗ 📂 telemetry/ # 👁️🗨️ 玻璃房计划:Cloudflare Workers 透明计数器网关源码
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 极速部署 (Quick Start)
|
## 🚀 极速部署 (Quick Start)
|
||||||
|
|
||||||
v3.1.x 提供了两种接入模式,请根据您的战术需求选择:
|
v3.5.x 提供了两种接入模式,请根据您的战术需求选择:
|
||||||
|
|
||||||
### 🔹 模式 A:官方公共模式 (最简、推荐)
|
### 🔹 模式 A:官方公共模式 (最简、推荐)
|
||||||
**适合不想折腾、只想快速养护 IP 的新兵。**
|
**适合不想折腾、只想快速养护 IP 的新兵。**
|
||||||
@@ -60,7 +65,6 @@ bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/i
|
|||||||
```Bash
|
```Bash
|
||||||
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/master/install_master.sh)
|
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/master/install_master.sh)
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
2. **部署 Agent**:在需要养护的机器上执行 Agent 脚本,输入您自建机器人的 Token 以及与 Master 一致的配置。
|
2. **部署 Agent**:在需要养护的机器上执行 Agent 脚本,输入您自建机器人的 Token 以及与 Master 一致的配置。
|
||||||
```Bash
|
```Bash
|
||||||
@@ -69,6 +73,18 @@ bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/i
|
|||||||
```
|
```
|
||||||
3. **激活节点**:同上,将暗号转发给您自己的机器人即可。
|
3. **激活节点**:同上,将暗号转发给您自己的机器人即可。
|
||||||
|
|
||||||
|
### ⚠️ 架构级热升级指引 (Upgrade to v3.5.0)
|
||||||
|
|
||||||
|
得益于 **v3.5.0 全新引入的 SSOT 版本锚点与状态机路由**,系统升级现已变得极其智能化。
|
||||||
|
|
||||||
|
**如果您是从远古旧版 (v3.3.1 / v3.3.2) 升级:**
|
||||||
|
1. 在终端再次运行对应的官方部署指令。
|
||||||
|
2. 脚本会识别到您处于“前版本锚点时代”,会自动为您执行【跨代架构重组】。
|
||||||
|
3. **关键动作**:由于节点命名防撞机制变更,升级后您的 TG 会收到一条新的 `#REGISTER#` 指令,请点击并发送一次以同步新身份。
|
||||||
|
4. **清理**:在面板中手动剔除失联的旧节点即可。
|
||||||
|
|
||||||
|
**如果您已处于 v3.4.0+:**
|
||||||
|
所有的升级已进入**“极致静默平滑模式”**。安装引擎会动态抓取云端 `version.txt`,自动修正本地 `config.conf` 的版本号,一键回车,3 秒即可完成全系组件的热重载换代!
|
||||||
|
|
||||||
🗑️ 一键无痕卸载
|
🗑️ 一键无痕卸载
|
||||||
如果你需要清理某个边缘节点,只需重新运行 `core/install.sh` 并选择 **[2]**,或直接在节点终端执行:
|
如果你需要清理某个边缘节点,只需重新运行 `core/install.sh` 并选择 **[2]**,或直接在节点终端执行:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 V3.0.3)
|
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 - 动态锚点版)
|
||||||
# 核心功能: 智能防打扰注册、进程自检、模块级路由分发(403拦截)
|
# 核心功能: 智能防打扰注册、进程自检、模块级路由分发(403拦截)
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -17,21 +17,30 @@ source "$CONFIG_FILE"
|
|||||||
|
|
||||||
# 默认 Webhook 监听端口
|
# 默认 Webhook 监听端口
|
||||||
AGENT_PORT=${AGENT_PORT:-9527}
|
AGENT_PORT=${AGENT_PORT:-9527}
|
||||||
NODE_NAME=$(hostname | cut -c 1-15)
|
# [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}"
|
||||||
|
|
||||||
# --- [重点升级 1: 守护进程防冲突自检] ---
|
# --- [重点升级 1: 守护进程防冲突自检] ---
|
||||||
if pgrep -f "webhook.py $AGENT_PORT" > /dev/null; then
|
if pgrep -f "webhook.py $AGENT_PORT" > /dev/null; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 1. [v3.0.1修复] 严格按照 install.sh 锁定的网络协议 (v4/v6) 获取 IP
|
# 1. 尝试获取实时公网 IP
|
||||||
RAW_IP=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip | tr -d '[:space:]')
|
RAW_IP=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip | tr -d '[:space:]')
|
||||||
|
|
||||||
# 为新获取到的 v6 自动加方括号,以确保与之前锁定的格式对齐比对
|
# [v3.3.1 修改] 为新获取到的 v6 自动加方括号;如果网络波动没抓到,强制信任本地 config 中的公网面孔
|
||||||
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
|
if [ -n "$RAW_IP" ]; then
|
||||||
AGENT_IP="[${RAW_IP}]"
|
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
|
||||||
|
AGENT_IP="[${RAW_IP}]"
|
||||||
|
else
|
||||||
|
AGENT_IP="$RAW_IP"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
AGENT_IP="$RAW_IP"
|
AGENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$AGENT_IP" ]; then
|
if [ -n "$AGENT_IP" ]; then
|
||||||
@@ -41,8 +50,8 @@ if [ -n "$AGENT_IP" ]; then
|
|||||||
|
|
||||||
# 只有当这是第一次运行,或者公网 IP 发生变动时,才发送 Telegram 申请
|
# 只有当这是第一次运行,或者公网 IP 发生变动时,才发送 Telegram 申请
|
||||||
if [ "$AGENT_IP" != "$LAST_IP" ]; then
|
if [ "$AGENT_IP" != "$LAST_IP" ]; then
|
||||||
# V3.1.3 协议升级: 在底部暗号中精准嵌入 ${REGION_CODE} 大区标识
|
# [v3.5.2 核心] 携带 6 字段双轨身份发起注册申请 (展示别名,暗号尾部追加 NODE_ALIAS)
|
||||||
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}\`"
|
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}\`"
|
||||||
|
|
||||||
curl -s -m 5 -X POST "${TG_API_URL}" \
|
curl -s -m 5 -X POST "${TG_API_URL}" \
|
||||||
-d "chat_id=${CHAT_ID}" \
|
-d "chat_id=${CHAT_ID}" \
|
||||||
@@ -200,8 +209,11 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
if lines:
|
if lines:
|
||||||
log_data = html.escape("".join(lines[-15:]))
|
log_data = html.escape("".join(lines[-15:]))
|
||||||
|
|
||||||
node_name = subprocess.check_output(['hostname']).decode('utf-8').strip()[:15]
|
# [v3.5.2 核心] 获取版本与节点展示别名
|
||||||
text_msg = f"📄 <b>[{node_name}] 实时运行日志:</b>\n<pre><code>{log_data}</code></pre>"
|
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>"
|
||||||
|
|
||||||
data = urllib.parse.urlencode({
|
data = urllib.parse.urlencode({
|
||||||
'chat_id': config.get('CHAT_ID', ''),
|
'chat_id': config.get('CHAT_ID', ''),
|
||||||
@@ -212,13 +224,116 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
config.get('TG_API_URL', ''),
|
config.get('TG_API_URL', ''),
|
||||||
data=data,
|
data=data,
|
||||||
headers={'User-Agent': 'IP-Sentinel-Agent/3.0.4'}
|
# [动态化] 彻底消灭硬编码,使用运行态版本号
|
||||||
|
headers={'User-Agent': f'IP-Sentinel-Agent/{local_ver}'}
|
||||||
)
|
)
|
||||||
urllib.request.urlopen(req, timeout=10)
|
urllib.request.urlopen(req, timeout=10)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Log transmission failed: {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
|
||||||
|
config_path = '/opt/ip_sentinel/config.conf'
|
||||||
|
with open(config_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
alias_found = False
|
||||||
|
config_dict = {}
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith('NODE_ALIAS='):
|
||||||
|
lines[i] = f'NODE_ALIAS="{safe_alias}"\n'
|
||||||
|
alias_found = True
|
||||||
|
elif '=' in line and not line.startswith('#'):
|
||||||
|
key, val = line.strip().split('=', 1)
|
||||||
|
config_dict[key] = val.strip('"\'')
|
||||||
|
|
||||||
|
if not alias_found:
|
||||||
|
lines.append(f'NODE_ALIAS="{safe_alias}"\n')
|
||||||
|
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
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'
|
||||||
|
with open(config_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
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')
|
||||||
|
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
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:
|
else:
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|||||||
684
core/install.sh
684
core/install.sh
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 v3.0.3 - Global Nexus)
|
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 - 动态锚点版)
|
||||||
# 核心功能: 区域选择、模块按需开启、官方机器人一键配置
|
# 核心功能: 战区分组菜单、模块按需开启、官方机器人一键配置、版本状态机路由
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# 你的 GitHub 仓库 Raw 数据直链前缀
|
# 你的 GitHub 仓库 Raw 数据直链前缀
|
||||||
@@ -12,8 +12,19 @@ REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
|||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
CONFIG_FILE="${INSTALL_DIR}/config.conf"
|
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"}
|
||||||
|
|
||||||
|
# 轻量级版本号比对函数 (例如: 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"
|
||||||
|
}
|
||||||
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
echo " 🛡️ 欢迎使用 IP-Sentinel (边缘节点 Edge Agent)"
|
echo " 🛡️ 欢迎使用 IP-Sentinel (边缘节点 Edge Agent)"
|
||||||
|
echo " 当前安装包版本: v${TARGET_VERSION}"
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
|
|
||||||
# 1. 依赖检查与安装 (新增 python3 用于轻量级 Webhook 服务)
|
# 1. 依赖检查与安装 (新增 python3 用于轻量级 Webhook 服务)
|
||||||
@@ -42,6 +53,9 @@ echo " 1) 🚀 部署边缘节点 (进入全球节点配置)"
|
|||||||
echo " 2) 🗑️ 一键卸载 IP-Sentinel"
|
echo " 2) 🗑️ 一键卸载 IP-Sentinel"
|
||||||
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
||||||
|
|
||||||
|
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定
|
||||||
|
ACTION_CHOICE=${ACTION_CHOICE:-1}
|
||||||
|
|
||||||
if [ "$ACTION_CHOICE" == "2" ]; then
|
if [ "$ACTION_CHOICE" == "2" ]; then
|
||||||
echo -e "\n⏳ 正在拉取卸载程序..."
|
echo -e "\n⏳ 正在拉取卸载程序..."
|
||||||
curl -sL "${REPO_RAW_URL}/core/uninstall.sh" -o "/tmp/ip_uninstall.sh"
|
curl -sL "${REPO_RAW_URL}/core/uninstall.sh" -o "/tmp/ip_uninstall.sh"
|
||||||
@@ -51,8 +65,31 @@ if [ "$ACTION_CHOICE" == "2" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ================== [v3.1.1 新增: 安装前环境纯净度清理 (严格保留日志)] ==================
|
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
||||||
echo -e "\n⏳ 正在清理旧版守护进程与冗余任务 (保留历史日志)..."
|
UPGRADE_MODE="false"
|
||||||
|
KEEP_LOGS="true"
|
||||||
|
|
||||||
|
if [ "$ACTION_CHOICE" == "1" ] && [ -f "$CONFIG_FILE" ]; then
|
||||||
|
echo -e "\n\033[33m💡 哨兵雷达提示:检测到本机已部署过 IP-Sentinel。\033[0m"
|
||||||
|
read -p "👉 是否按原配置直接进行平滑升级?(y/n, 默认y): " UPGRADE_CHOICE
|
||||||
|
if [[ -z "$UPGRADE_CHOICE" || "$UPGRADE_CHOICE" =~ ^[Yy]$ ]]; then
|
||||||
|
UPGRADE_MODE="true"
|
||||||
|
read -p "👉 是否保留历史运行日志?(y/n, 默认y): " LOG_CHOICE
|
||||||
|
if [[ "$LOG_CHOICE" =~ ^[Nn]$ ]]; then
|
||||||
|
KEEP_LOGS="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 将原配置读入环境变量,为后续跳过配置步骤提供燃料
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
echo -e "\033[32m✅ 已激活 [平滑升级模式],即将跳过基础配置,直接更新核心装甲...\033[0m"
|
||||||
|
else
|
||||||
|
echo -e "\033[33m🔄 您选择了重新配置,旧的哨兵数据将被彻底抹除。\033[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# ================== [v3.1.1/v3.2.2 优化: 安装前环境纯净度清理] ==================
|
||||||
|
echo -e "\n⏳ 正在清理旧版守护进程与冗余任务..."
|
||||||
# 1. 强制超度可能存活的 Webhook 及各类看门狗进程,释放端口
|
# 1. 强制超度可能存活的 Webhook 及各类看门狗进程,释放端口
|
||||||
pkill -9 -f "webhook.py" >/dev/null 2>&1 || true
|
pkill -9 -f "webhook.py" >/dev/null 2>&1 || true
|
||||||
pkill -9 -f "agent_daemon.sh" >/dev/null 2>&1 || true
|
pkill -9 -f "agent_daemon.sh" >/dev/null 2>&1 || true
|
||||||
@@ -65,237 +102,299 @@ if crontab -l >/dev/null 2>&1; then
|
|||||||
rm -f /tmp/cron_clean
|
rm -f /tmp/cron_clean
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. 抹除旧版核心代码与配置文件,杜绝代码冲突 (精准避开 logs 目录)
|
# 3. 抹除旧版核心代码,杜绝代码冲突 (根据模式分流)
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
rm -rf "${INSTALL_DIR}/core" "${INSTALL_DIR}/data" "${INSTALL_DIR}/config.conf" "${INSTALL_DIR}/.last_ip" 2>/dev/null
|
# 升级模式:仅销毁核心引擎,严格保留 config 与 data
|
||||||
|
rm -rf "${INSTALL_DIR}/core" 2>/dev/null
|
||||||
|
if [ "$KEEP_LOGS" == "false" ]; then
|
||||||
|
rm -rf "${INSTALL_DIR}/logs" 2>/dev/null
|
||||||
|
echo -e "🗑️ 历史日志已按指令清空。"
|
||||||
|
else
|
||||||
|
echo -e "📦 历史配置与战地日志已妥善保留。"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 全新安装模式:焦土政策,彻底抹除
|
||||||
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
|
rm -rf "${INSTALL_DIR}/core" "${INSTALL_DIR}/data" "${INSTALL_DIR}/config.conf" "${INSTALL_DIR}/.last_ip" 2>/dev/null
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
echo -e "\033[32m✅ 环境清理完毕,幽灵进程已肃清!\033[0m"
|
echo -e "\033[32m✅ 环境清理完毕,幽灵进程已肃清!\033[0m"
|
||||||
# ========================================================================================
|
# ========================================================================================
|
||||||
|
|
||||||
# 📍 动态一级菜单:国家选择
|
# ==========================================================
|
||||||
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=()
|
if [ "$UPGRADE_MODE" == "false" ]; then
|
||||||
while IFS="|" read -r c_id c_name k_file; do
|
|
||||||
echo " $i) $c_name"
|
|
||||||
COUNTRY_MAP[$i]="$c_id"
|
|
||||||
KEYWORD_MAP[$i]="$k_file"
|
|
||||||
((i++))
|
|
||||||
done < /tmp/countries.txt
|
|
||||||
|
|
||||||
read -p "请输入选择 [1-$((i-1))] (默认1): " C_SEL
|
# 📍 动态零级菜单:战区(大洲)选择
|
||||||
C_SEL=${C_SEL:-1}
|
echo -e "\n\033[36m📍 【第零级】请选择目标战区 (Continent):\033[0m"
|
||||||
COUNTRY_ID="${COUNTRY_MAP[$C_SEL]}"
|
jq -r '.continents[] | "\(.id)|\(.name)"' /tmp/map.json > /tmp/continents.txt
|
||||||
KEYWORD_FILE="${KEYWORD_MAP[$C_SEL]}"
|
i=1; CONT_MAP=()
|
||||||
REGION_CODE="$COUNTRY_ID" # 兼容旧版的 config.conf
|
while IFS="|" read -r cont_id cont_name; do
|
||||||
|
echo " $i) $cont_name"
|
||||||
# 📍 动态二级菜单:省/州选择
|
CONT_MAP[$i]="$cont_id"
|
||||||
echo -e "\n\033[36m📍 【第二级】正在检索 [$COUNTRY_ID] 的行政区数据...\033[0m"
|
|
||||||
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
|
|
||||||
IFS="|" read -r STATE_ID STATE_NAME < /tmp/states.txt
|
|
||||||
echo -e "\033[32m💡 该国家下仅有单一配置 [$STATE_NAME],已自动跃迁。\033[0m"
|
|
||||||
else
|
|
||||||
i=1; STATE_MAP=()
|
|
||||||
while IFS="|" read -r s_id s_name; do
|
|
||||||
echo " $i) $s_name"
|
|
||||||
STATE_MAP[$i]="$s_id"
|
|
||||||
((i++))
|
((i++))
|
||||||
done < /tmp/states.txt
|
done < /tmp/continents.txt
|
||||||
read -p "请输入选择 [1-$((i-1))] (默认1): " S_SEL
|
|
||||||
S_SEL=${S_SEL:-1}
|
|
||||||
STATE_ID="${STATE_MAP[$S_SEL]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 📍 动态三级菜单:城市选择
|
read -p "请输入选择 [1-$((i-1))] (默认1): " CONT_SEL
|
||||||
echo -e "\n\033[36m📍 【第三级】请锁定具体城市节点:\033[0m"
|
CONT_SEL=${CONT_SEL:-1}
|
||||||
jq -r ".countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | select(.id==\"$STATE_ID\") | .cities[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/cities.txt
|
CONT_ID="${CONT_MAP[$CONT_SEL]}"
|
||||||
CITY_COUNT=$(wc -l < /tmp/cities.txt)
|
|
||||||
|
|
||||||
if [ "$CITY_COUNT" -eq 1 ]; then
|
# 📍 动态一级菜单:国家选择 (基于选中战区)
|
||||||
IFS="|" read -r CITY_ID CITY_NAME < /tmp/cities.txt
|
echo -e "\n\033[36m📍 【第一级】正在检索 [$CONT_ID] 战区下的国家/地区...\033[0m"
|
||||||
echo -e "\033[32m💡 该区域下仅有单一城市 [$CITY_NAME],已自动锁定。\033[0m"
|
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | \"\(.id)|\(.name)|\(.keyword_file)\"" /tmp/map.json > /tmp/countries.txt
|
||||||
else
|
i=1; COUNTRY_MAP=(); KEYWORD_MAP=()
|
||||||
i=1; CITY_MAP=()
|
while IFS="|" read -r c_id c_name k_file; do
|
||||||
while IFS="|" read -r c_id c_name; do
|
|
||||||
echo " $i) $c_name"
|
echo " $i) $c_name"
|
||||||
CITY_MAP[$i]="$c_id"
|
COUNTRY_MAP[$i]="$c_id"
|
||||||
|
KEYWORD_MAP[$i]="$k_file"
|
||||||
((i++))
|
((i++))
|
||||||
done < /tmp/cities.txt
|
done < /tmp/countries.txt
|
||||||
read -p "请输入选择 [1-$((i-1))] (默认1): " CI_SEL
|
|
||||||
CI_SEL=${CI_SEL:-1}
|
|
||||||
CITY_ID="${CITY_MAP[$CI_SEL]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理临时文件
|
read -p "请输入选择 [1-$((i-1))] (默认1): " C_SEL
|
||||||
rm -f /tmp/map.json /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
|
C_SEL=${C_SEL:-1}
|
||||||
|
COUNTRY_ID="${COUNTRY_MAP[$C_SEL]}"
|
||||||
|
KEYWORD_FILE="${KEYWORD_MAP[$C_SEL]}"
|
||||||
|
REGION_CODE="$COUNTRY_ID" # 兼容旧版的 config.conf
|
||||||
|
|
||||||
# 本地工作目录初始化 (支持 v3.0 的深度层级)
|
# 📍 动态二级菜单:省/州选择 (基于选中战区和国家)
|
||||||
mkdir -p "${INSTALL_DIR}/core"
|
echo -e "\n\033[36m📍 【第二级】正在检索 [$COUNTRY_ID] 的行政区数据...\033[0m"
|
||||||
mkdir -p "${INSTALL_DIR}/data/keywords"
|
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/states.txt
|
||||||
mkdir -p "${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}"
|
STATE_COUNT=$(wc -l < /tmp/states.txt)
|
||||||
mkdir -p "${INSTALL_DIR}/logs"
|
|
||||||
|
|
||||||
# 3. 功能模块前置开关 (按需加载)
|
if [ "$STATE_COUNT" -eq 1 ]; then
|
||||||
echo -e "\n[3/7] 请选择需要开启的养护模块 (按需开启,节省资源):"
|
IFS="|" read -r STATE_ID STATE_NAME < /tmp/states.txt
|
||||||
echo " 1) 📍 仅开启 [Google 区域纠偏] (默认,适合流媒体解锁机位漂移)"
|
echo -e "\033[32m💡 该国家下仅有单一配置 [$STATE_NAME],已自动跃迁。\033[0m"
|
||||||
echo " 2) 🛡️ 仅开启 [IP 信用净化] (适合高风险机房 IP 降低 Scamalytics 分数)"
|
|
||||||
echo " 3) 🔥 双管齐下 (同时开启以上两项)"
|
|
||||||
read -p "请输入选择 [1-3] (默认1): " MODULE_CHOICE
|
|
||||||
|
|
||||||
ENABLE_GOOGLE="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 司令部?(需要配置与主控相同的 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\033[33m💡 提示:您可以选择使用自己的机器人,或者直接回车使用官方公共机器人。\033[0m"
|
|
||||||
echo -e "\033[33m⚠️ 注意:若使用官方机器人,请务必先在 TG 中关注 @OmniBeacon_bot 并发送 /start\033[0m"
|
|
||||||
|
|
||||||
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👉 请确保您已关注官方机器人并发送过 /start,否则将无法接收消息。\033[0m"
|
|
||||||
else
|
else
|
||||||
TG_TOKEN="$USER_TOKEN"
|
i=1; STATE_MAP=()
|
||||||
TG_API_URL="https://api.telegram.org/bot${TG_TOKEN}/sendMessage"
|
while IFS="|" read -r s_id s_name; do
|
||||||
echo -e "\033[32m✅ 已记录您的私有机器人 Token。\033[0m"
|
echo " $i) $s_name"
|
||||||
|
STATE_MAP[$i]="$s_id"
|
||||||
|
((i++))
|
||||||
|
done < /tmp/states.txt
|
||||||
|
read -p "请输入选择 [1-$((i-1))] (默认1): " S_SEL
|
||||||
|
S_SEL=${S_SEL:-1}
|
||||||
|
STATE_ID="${STATE_MAP[$S_SEL]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\033[33m💡 提示:如果您不知道自己的 Chat ID,可以关注 @userinfobot 获取。\033[0m"
|
# 📍 动态三级菜单:城市选择 (基于战区、国家、州三层过滤)
|
||||||
read -p "请输入你的 Chat ID (与主控一致): " CHAT_ID
|
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
|
||||||
# ================== [v3.0.3 变更: 智能随机高位端口生成系统] ==================
|
CITY_COUNT=$(wc -l < /tmp/cities.txt)
|
||||||
echo -e "\n\033[36m[4.2/7] 正在构建 Webhook 安全通信隧道...\033[0m"
|
|
||||||
echo -n "🎲 正在探测可用随机端口..."
|
if [ "$CITY_COUNT" -eq 1 ]; then
|
||||||
while true; do
|
IFS="|" read -r CITY_ID CITY_NAME < /tmp/cities.txt
|
||||||
RANDOM_PORT=$((RANDOM % 55536 + 10000))
|
echo -e "\033[32m💡 该区域下仅有单一城市 [$CITY_NAME],已自动锁定。\033[0m"
|
||||||
# 同时兼容 ss (新) 和 netstat (旧) 检查端口占用
|
else
|
||||||
if ! (ss -tuln 2>/dev/null | grep -q ":$RANDOM_PORT " || netstat -tuln 2>/dev/null | grep -q ":$RANDOM_PORT "); then
|
i=1; CITY_MAP=()
|
||||||
break
|
while IFS="|" read -r c_id c_name; do
|
||||||
fi
|
echo " $i) $c_name"
|
||||||
echo -n "."
|
CITY_MAP[$i]="$c_id"
|
||||||
done
|
((i++))
|
||||||
echo -e " 完成!"
|
done < /tmp/cities.txt
|
||||||
|
read -p "请输入选择 [1-$((i-1))] (默认1): " CI_SEL
|
||||||
echo -e "💡 系统为您生成的推荐随机高位端口为: \033[32m$RANDOM_PORT\033[0m"
|
CI_SEL=${CI_SEL:-1}
|
||||||
echo -e "\033[33m(该端口已通过本地占用校验,可直接使用)\033[0m"
|
CITY_ID="${CITY_MAP[$CI_SEL]}"
|
||||||
|
fi
|
||||||
while true; do
|
|
||||||
read -p "请输入 Webhook 监听端口 (回车采用推荐, 或手动输入): " INPUT_PORT
|
# 清理临时文件 (增加清理 continents.txt)
|
||||||
|
rm -f /tmp/map.json /tmp/continents.txt /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
|
||||||
|
|
||||||
|
# 本地工作目录初始化 (支持 v3.0 的深度层级)
|
||||||
|
mkdir -p "${INSTALL_DIR}/core"
|
||||||
|
mkdir -p "${INSTALL_DIR}/data/keywords"
|
||||||
|
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 远程动态启停)..."
|
||||||
|
ENABLE_GOOGLE="true"
|
||||||
|
ENABLE_TRUST="true"
|
||||||
|
|
||||||
|
# 4. 接入 Master 中枢配置
|
||||||
|
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\033[33m💡 提示:您可以选择使用自己的机器人,或者直接回车使用官方公共机器人。\033[0m"
|
||||||
|
echo -e "\033[33m⚠️ 注意:若使用官方机器人,请务必先在 TG 中关注 @OmniBeacon_bot 并发送 /start\033[0m"
|
||||||
|
|
||||||
if [ -z "$INPUT_PORT" ]; then
|
read -p "请输入您的 Telegram Bot Token (回车使用官方默认): " USER_TOKEN
|
||||||
AGENT_PORT="$RANDOM_PORT"
|
|
||||||
break
|
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👉 请确保您已关注官方机器人并发送过 /start,否则将无法接收消息。\033[0m"
|
||||||
else
|
else
|
||||||
# 校验手动输入的合法性与可用性
|
TG_TOKEN="$USER_TOKEN"
|
||||||
if [[ "$INPUT_PORT" =~ ^[0-9]+$ ]] && [ "$INPUT_PORT" -ge 1 ] && [ "$INPUT_PORT" -le 65535 ]; then
|
TG_API_URL="https://api.telegram.org/bot${TG_TOKEN}/sendMessage"
|
||||||
if (ss -tuln 2>/dev/null | grep -q ":$INPUT_PORT " || netstat -tuln 2>/dev/null | grep -q ":$INPUT_PORT "); then
|
echo -e "\033[32m✅ 已记录您的私有机器人 Token。\033[0m"
|
||||||
echo -e "\033[31m❌ 端口 $INPUT_PORT 已被占用,请重新输入或使用推荐端口。\033[0m"
|
fi
|
||||||
else
|
|
||||||
AGENT_PORT="$INPUT_PORT"
|
echo -e "\033[33m💡 提示:如果您不知道自己的 Chat ID,可以关注 @userinfobot 获取。\033[0m"
|
||||||
break
|
read -p "请输入你的 Chat ID (与主控一致): " CHAT_ID
|
||||||
fi
|
|
||||||
else
|
# ================== [v3.0.3 变更: 智能随机高位端口生成系统] ==================
|
||||||
echo -e "\033[31m❌ 输入非法!端口范围应为 1-65535。\033[0m"
|
echo -e "\n\033[36m[4.2/7] 正在构建 Webhook 安全通信隧道...\033[0m"
|
||||||
|
echo -n "🎲 正在探测可用随机端口..."
|
||||||
|
while true; do
|
||||||
|
RANDOM_PORT=$((RANDOM % 55536 + 10000))
|
||||||
|
# 同时兼容 ss (新) 和 netstat (旧) 检查端口占用
|
||||||
|
if ! (ss -tuln 2>/dev/null | grep -q ":$RANDOM_PORT " || netstat -tuln 2>/dev/null | grep -q ":$RANDOM_PORT "); then
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
echo -n "."
|
||||||
done
|
done
|
||||||
echo -e "✅ 已锁定 Webhook 通讯端口: \033[32m$AGENT_PORT\033[0m"
|
echo -e " 完成!"
|
||||||
# ====================================================================
|
|
||||||
fi
|
echo -e "💡 系统为您生成的推荐随机高位端口为: \033[32m$RANDOM_PORT\033[0m"
|
||||||
|
echo -e "\033[33m(该端口已通过本地占用校验,可直接使用)\033[0m"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -p "请输入 Webhook 监听端口 (回车采用推荐, 或手动输入): " INPUT_PORT
|
||||||
|
|
||||||
|
if [ -z "$INPUT_PORT" ]; then
|
||||||
|
AGENT_PORT="$RANDOM_PORT"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
# 校验手动输入的合法性与可用性
|
||||||
|
if [[ "$INPUT_PORT" =~ ^[0-9]+$ ]] && [ "$INPUT_PORT" -ge 1 ] && [ "$INPUT_PORT" -le 65535 ]; then
|
||||||
|
if (ss -tuln 2>/dev/null | grep -q ":$INPUT_PORT " || netstat -tuln 2>/dev/null | grep -q ":$INPUT_PORT "); then
|
||||||
|
echo -e "\033[31m❌ 端口 $INPUT_PORT 已被占用,请重新输入或使用推荐端口。\033[0m"
|
||||||
|
else
|
||||||
|
AGENT_PORT="$INPUT_PORT"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "\033[31m❌ 输入非法!端口范围应为 1-65535。\033[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo -e "✅ 已锁定 Webhook 通讯端口: \033[32m$AGENT_PORT\033[0m"
|
||||||
|
# ====================================================================
|
||||||
|
fi
|
||||||
|
|
||||||
# ================== [v3.0.1新增修改 1: 冗余网络栈探测与锚点锁定] ==================
|
# ================== [v3.0.1新增修改 1: 冗余网络栈探测与锚点锁定] ==================
|
||||||
echo -e "\n\033[36m[4.5/7] 正在探测本机网络栈与可用出口 (多节点雷达扫描中)...\033[0m"
|
echo -e "\n\033[36m[4.5/7] 正在探测本机网络栈与可用出口 (多节点雷达扫描中)...\033[0m"
|
||||||
|
|
||||||
# 引入容灾机制:依次尝试三个不同的 API,拿到有效的 IP 格式就停止
|
# 引入容灾机制:依次尝试三个不同的 API,拿到有效的 IP 格式就停止
|
||||||
DETECT_V4=$( (curl -4 -s -m 3 api.ip.sb/ip || curl -4 -s -m 3 ifconfig.me || curl -4 -s -m 3 ipv4.icanhazip.com) 2>/dev/null | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -n 1 | tr -d '[:space:]')
|
DETECT_V4=$( (curl -4 -s -m 3 api.ip.sb/ip || curl -4 -s -m 3 ifconfig.me || curl -4 -s -m 3 ipv4.icanhazip.com) 2>/dev/null | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -n 1 | tr -d '[:space:]')
|
||||||
DETECT_V6=$( (curl -6 -s -m 3 api.ip.sb/ip || curl -6 -s -m 3 ifconfig.me || curl -6 -s -m 3 ipv6.icanhazip.com) 2>/dev/null | grep -E "^[0-9a-fA-F:]+.*:" | head -n 1 | tr -d '[:space:]')
|
DETECT_V6=$( (curl -6 -s -m 3 api.ip.sb/ip || curl -6 -s -m 3 ifconfig.me || curl -6 -s -m 3 ipv6.icanhazip.com) 2>/dev/null | grep -E "^[0-9a-fA-F:]+.*:" | head -n 1 | tr -d '[:space:]')
|
||||||
|
|
||||||
# 构建动态选项数组
|
# 构建动态选项数组
|
||||||
IP_OPTIONS=()
|
IP_OPTIONS=()
|
||||||
IP_PROTO=()
|
IP_PROTO=()
|
||||||
|
|
||||||
[[ -n "$DETECT_V4" ]] && { IP_OPTIONS+=("$DETECT_V4"); IP_PROTO+=("4"); }
|
[[ -n "$DETECT_V4" ]] && { IP_OPTIONS+=("$DETECT_V4"); IP_PROTO+=("4"); }
|
||||||
[[ -n "$DETECT_V6" ]] && { IP_OPTIONS+=("$DETECT_V6"); IP_PROTO+=("6"); }
|
[[ -n "$DETECT_V6" ]] && { IP_OPTIONS+=("$DETECT_V6"); IP_PROTO+=("6"); }
|
||||||
|
|
||||||
if [ ${#IP_OPTIONS[@]} -eq 0 ]; then
|
if [ ${#IP_OPTIONS[@]} -eq 0 ]; then
|
||||||
echo -e "\033[33m⚠️ 雷达受阻:未能自动探测到公网 IP,请手动指定。\033[0m"
|
echo -e "\033[33m⚠️ 雷达受阻:未能自动探测到公网 IP,请手动指定。\033[0m"
|
||||||
read -p "请输入您要绑定的公网 IP (v4 或 v6): " PUBLIC_IP
|
|
||||||
[[ "$PUBLIC_IP" == *":"* ]] && IP_PREF="6" || IP_PREF="4"
|
|
||||||
else
|
|
||||||
echo "📍 发现可用出口 IP,请选择要注册与养护的锚点:"
|
|
||||||
for i in "${!IP_OPTIONS[@]}"; do
|
|
||||||
num=$((i+1))
|
|
||||||
if [ "${IP_PROTO[$i]}" == "4" ]; then
|
|
||||||
echo " $num) 🌐 IPv4: ${IP_OPTIONS[$i]} (默认选项)"
|
|
||||||
else
|
|
||||||
echo " $num) 🌌 IPv6: ${IP_OPTIONS[$i]}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
CUSTOM_OPT=$(( ${#IP_OPTIONS[@]} + 1 ))
|
|
||||||
echo " $CUSTOM_OPT) ✍️ 手动指定其他 IP (适合多 IP 站群机)"
|
|
||||||
|
|
||||||
read -p "请输入选择 (默认1): " IP_CHOICE
|
|
||||||
IP_CHOICE=${IP_CHOICE:-1}
|
|
||||||
|
|
||||||
if [ "$IP_CHOICE" -le "${#IP_OPTIONS[@]}" ] && [ "$IP_CHOICE" -gt 0 ]; then
|
|
||||||
idx=$((IP_CHOICE-1))
|
|
||||||
PUBLIC_IP="${IP_OPTIONS[$idx]}"
|
|
||||||
IP_PREF="${IP_PROTO[$idx]}"
|
|
||||||
elif [ "$IP_CHOICE" -eq "$CUSTOM_OPT" ]; then
|
|
||||||
read -p "请输入您要绑定的公网 IP (v4 或 v6): " PUBLIC_IP
|
read -p "请输入您要绑定的公网 IP (v4 或 v6): " PUBLIC_IP
|
||||||
[[ "$PUBLIC_IP" == *":"* ]] && IP_PREF="6" || IP_PREF="4"
|
[[ "$PUBLIC_IP" == *":"* ]] && IP_PREF="6" || IP_PREF="4"
|
||||||
else
|
else
|
||||||
# 兜底:乱输就默认选第一个
|
echo "📍 发现可用出口 IP,请选择要注册与养护的锚点:"
|
||||||
PUBLIC_IP="${IP_OPTIONS[0]}"
|
for i in "${!IP_OPTIONS[@]}"; do
|
||||||
IP_PREF="${IP_PROTO[0]}"
|
num=$((i+1))
|
||||||
|
if [ "${IP_PROTO[$i]}" == "4" ]; then
|
||||||
|
echo " $num) 🌐 IPv4: ${IP_OPTIONS[$i]} (默认选项)"
|
||||||
|
else
|
||||||
|
echo " $num) 🌌 IPv6: ${IP_OPTIONS[$i]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
CUSTOM_OPT=$(( ${#IP_OPTIONS[@]} + 1 ))
|
||||||
|
echo " $CUSTOM_OPT) ✍️ 手动指定其他 IP (适合多 IP 站群机)"
|
||||||
|
|
||||||
|
read -p "请输入选择 (默认1): " IP_CHOICE
|
||||||
|
IP_CHOICE=${IP_CHOICE:-1}
|
||||||
|
|
||||||
|
if [ "$IP_CHOICE" -le "${#IP_OPTIONS[@]}" ] && [ "$IP_CHOICE" -gt 0 ]; then
|
||||||
|
idx=$((IP_CHOICE-1))
|
||||||
|
PUBLIC_IP="${IP_OPTIONS[$idx]}"
|
||||||
|
IP_PREF="${IP_PROTO[$idx]}"
|
||||||
|
elif [ "$IP_CHOICE" -eq "$CUSTOM_OPT" ]; then
|
||||||
|
read -p "请输入您要绑定的公网 IP (v4 或 v6): " PUBLIC_IP
|
||||||
|
[[ "$PUBLIC_IP" == *":"* ]] && IP_PREF="6" || IP_PREF="4"
|
||||||
|
else
|
||||||
|
# 兜底:乱输就默认选第一个
|
||||||
|
PUBLIC_IP="${IP_OPTIONS[0]}"
|
||||||
|
IP_PREF="${IP_PROTO[0]}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
# 终极修复:为 IPv6 自动穿上防护装甲(方括号),解决 Master 拼接 URL 报错问题
|
# ================== [v3.3.1 核心重构: 身份剥离与双栈实弹嗅探] ==================
|
||||||
if [[ "$PUBLIC_IP" == *":"* ]] && [[ "$PUBLIC_IP" != *"["* ]]; then
|
# 1. 固化对外通讯身份 (自动穿透方括号护甲)
|
||||||
BIND_IP="[${PUBLIC_IP}]"
|
if [[ "$PUBLIC_IP" == *":"* ]] && [[ "$PUBLIC_IP" != *"["* ]]; then
|
||||||
else
|
SAFE_PUBLIC_IP="[${PUBLIC_IP}]"
|
||||||
BIND_IP="$PUBLIC_IP"
|
else
|
||||||
fi
|
SAFE_PUBLIC_IP="$PUBLIC_IP"
|
||||||
echo -e "\033[32m✅ 哨兵锚点已永久锁定至: $BIND_IP\033[0m"
|
fi
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
# 5. 远程拉取冷数据并解析固化
|
# 2. 实弹打靶测试 (NAT 环境嗅探与双栈自适应)
|
||||||
echo -e "\n[5/7] 正在从云端数据仓库拉取 [${CITY_NAME}] 节点的底层规则..."
|
echo -n "🕵️ 正在进行出站链路试射 (NAT环境与双栈嗅探)..."
|
||||||
REGION_JSON_FILE="${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}/${CITY_ID}.json"
|
RAW_TEST_IP=$(echo "$SAFE_PUBLIC_IP" | tr -d '[]')
|
||||||
curl -sL "${REPO_RAW_URL}/data/regions/${COUNTRY_ID}/${STATE_ID}/${CITY_ID}.json" -o "$REGION_JSON_FILE"
|
|
||||||
|
# 智能切换靶机: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"
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
if [ ! -s "$REGION_JSON_FILE" ]; then
|
# ================== [v3.5.2 新增: 节点不可变主键与展示别名] ==================
|
||||||
echo "❌ 拉取或解析规则失败!请检查 Forgejo 仓库是否公开或网络是否畅通。"
|
IP_HASH=$(echo "${SAFE_PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
|
||||||
exit 1
|
NODE_NAME="$(hostname | cut -c 1-10)-${IP_HASH}"
|
||||||
fi
|
NODE_ALIAS="$NODE_NAME"
|
||||||
|
|
||||||
# 使用 jq 提取 JSON 里的核心值
|
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
||||||
REGION_NAME=$(jq -r '.region_name' "$REGION_JSON_FILE")
|
echo -e "\n\033[36m[4.8/7] 节点展示别名设定 (用于面板友好显示)...\033[0m"
|
||||||
BASE_LAT=$(jq -r '.google_module.base_lat' "$REGION_JSON_FILE")
|
echo -e "💡 系统底层的不可变主键为: \033[33m${NODE_NAME}\033[0m"
|
||||||
BASE_LON=$(jq -r '.google_module.base_lon' "$REGION_JSON_FILE")
|
read -p "请输入节点展示别名 (如'纽约机房', 回车使用默认): " CUSTOM_ALIAS
|
||||||
LANG_PARAMS=$(jq -r '.google_module.lang_params' "$REGION_JSON_FILE")
|
|
||||||
VALID_URL_SUFFIX=$(jq -r '.google_module.valid_url_suffix' "$REGION_JSON_FILE")
|
|
||||||
|
|
||||||
# 写入本地静态配置文件
|
if [ -n "$CUSTOM_ALIAS" ]; then
|
||||||
cat > "$CONFIG_FILE" << EOF
|
# 🛡️ 强制字符清洗:防御 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"
|
||||||
|
fi
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
# 5. 远程拉取冷数据并解析固化
|
||||||
|
echo -e "\n[5/7] 正在从云端数据仓库拉取 [${CITY_NAME}] 节点的底层规则..."
|
||||||
|
REGION_JSON_FILE="${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}/${CITY_ID}.json"
|
||||||
|
curl -sL "${REPO_RAW_URL}/data/regions/${COUNTRY_ID}/${STATE_ID}/${CITY_ID}.json" -o "$REGION_JSON_FILE"
|
||||||
|
|
||||||
|
if [ ! -s "$REGION_JSON_FILE" ]; then
|
||||||
|
echo "❌ 拉取或解析规则失败!请检查 Forgejo 仓库是否公开或网络是否畅通。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 使用 jq 提取 JSON 里的核心值
|
||||||
|
REGION_NAME=$(jq -r '.region_name' "$REGION_JSON_FILE")
|
||||||
|
BASE_LAT=$(jq -r '.google_module.base_lat' "$REGION_JSON_FILE")
|
||||||
|
BASE_LON=$(jq -r '.google_module.base_lon' "$REGION_JSON_FILE")
|
||||||
|
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'))
|
# IP-Sentinel 本地固化配置 (生成时间: $(date '+%Y-%m-%d %H:%M:%S'))
|
||||||
|
AGENT_VERSION="$TARGET_VERSION"
|
||||||
REGION_CODE="$REGION_CODE"
|
REGION_CODE="$REGION_CODE"
|
||||||
REGION_NAME="$REGION_NAME"
|
REGION_NAME="$REGION_NAME"
|
||||||
BASE_LAT="$BASE_LAT"
|
BASE_LAT="$BASE_LAT"
|
||||||
@@ -314,17 +413,84 @@ AGENT_PORT="$AGENT_PORT"
|
|||||||
INSTALL_DIR="$INSTALL_DIR"
|
INSTALL_DIR="$INSTALL_DIR"
|
||||||
LOG_FILE="${INSTALL_DIR}/logs/sentinel.log"
|
LOG_FILE="${INSTALL_DIR}/logs/sentinel.log"
|
||||||
|
|
||||||
# [v3.0.1新增修改 2: 网络栈锚点锁定配置,供其他脚本读取]
|
# [v3.3.1修改: 双核身份剥离配置]
|
||||||
IP_PREF="$IP_PREF"
|
IP_PREF="$IP_PREF"
|
||||||
|
PUBLIC_IP="$SAFE_PUBLIC_IP"
|
||||||
BIND_IP="$BIND_IP"
|
BIND_IP="$BIND_IP"
|
||||||
|
|
||||||
|
# [v3.5.2新增: 双轨身份系统]
|
||||||
|
NODE_NAME="$NODE_NAME"
|
||||||
|
NODE_ALIAS="$NODE_ALIAS"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# ================== [v3.0.3 变更: 敏感配置文件权限收敛] ==================
|
# ================== [v3.0.3 变更: 敏感配置文件权限收敛] ==================
|
||||||
chmod 600 "$CONFIG_FILE"
|
chmod 600 "$CONFIG_FILE"
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
|
|
||||||
|
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 | 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. 拉取全套组件 (按需下载,绝不浪费空间)
|
# 6. 拉取全套组件 (按需下载,绝不浪费空间)
|
||||||
echo -e "\n[6/7] 正在根据模块开关部署核心引擎与热数据..."
|
echo -e "\n[6/7] 正在根据模块开关部署核心引擎与热数据..."
|
||||||
|
# 确保目录在升级模式下也能被正确建立
|
||||||
|
mkdir -p "${INSTALL_DIR}/core"
|
||||||
|
mkdir -p "${INSTALL_DIR}/data/keywords"
|
||||||
|
|
||||||
# 基础公共组件
|
# 基础公共组件
|
||||||
curl -sL "${REPO_RAW_URL}/core/runner.sh" -o "${INSTALL_DIR}/core/runner.sh"
|
curl -sL "${REPO_RAW_URL}/core/runner.sh" -o "${INSTALL_DIR}/core/runner.sh"
|
||||||
curl -sL "${REPO_RAW_URL}/core/updater.sh" -o "${INSTALL_DIR}/core/updater.sh"
|
curl -sL "${REPO_RAW_URL}/core/updater.sh" -o "${INSTALL_DIR}/core/updater.sh"
|
||||||
@@ -336,8 +502,13 @@ curl -sL "${REPO_RAW_URL}/data/user_agents.txt" -o "${INSTALL_DIR}/data/user_age
|
|||||||
# 动态按需组件
|
# 动态按需组件
|
||||||
if [ "$ENABLE_GOOGLE" == "true" ]; then
|
if [ "$ENABLE_GOOGLE" == "true" ]; then
|
||||||
curl -sL "${REPO_RAW_URL}/core/mod_google.sh" -o "${INSTALL_DIR}/core/mod_google.sh"
|
curl -sL "${REPO_RAW_URL}/core/mod_google.sh" -o "${INSTALL_DIR}/core/mod_google.sh"
|
||||||
# 根据 map.json 动态匹配的词库文件进行下载
|
# [v3.2.2 修复] 动态匹配词库下载逻辑
|
||||||
curl -sL "${REPO_RAW_URL}/data/keywords/${KEYWORD_FILE}" -o "${INSTALL_DIR}/data/keywords/${KEYWORD_FILE}"
|
if [ "$UPGRADE_MODE" == "false" ]; then
|
||||||
|
curl -sL "${REPO_RAW_URL}/data/keywords/${KEYWORD_FILE}" -o "${INSTALL_DIR}/data/keywords/${KEYWORD_FILE}"
|
||||||
|
else
|
||||||
|
# 升级模式:利用已有的 REGION_CODE 更新通用词库
|
||||||
|
curl -sL "${REPO_RAW_URL}/data/keywords/kw_${REGION_CODE}.txt" -o "${INSTALL_DIR}/data/keywords/kw_${REGION_CODE}.txt" 2>/dev/null || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ENABLE_TRUST" == "true" ]; then
|
if [ "$ENABLE_TRUST" == "true" ]; then
|
||||||
@@ -352,8 +523,11 @@ crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_backup
|
|||||||
|
|
||||||
# 核心养护模块: 每 30 分钟触发一次
|
# 核心养护模块: 每 30 分钟触发一次
|
||||||
echo "*/30 * * * * ${INSTALL_DIR}/core/runner.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
echo "*/30 * * * * ${INSTALL_DIR}/core/runner.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
||||||
# 养料更新模块: 每周日凌晨 3 点静默去云端更新热数据
|
# 养料更新模块: (v3.3.0升级) 每天凌晨 3 点触发,由中枢自动进行分频调度
|
||||||
echo "0 3 * * 0 ${INSTALL_DIR}/core/updater.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
echo "0 3 * * * ${INSTALL_DIR}/core/updater.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
||||||
|
|
||||||
|
# [v3.3.0 新增] 初始化 UA 指纹库更新时间戳,确立 30 天滚动周期的计算锚点
|
||||||
|
echo $(date +%s) > "${INSTALL_DIR}/core/.ua_last_update"
|
||||||
|
|
||||||
# 如果配置了联控,启动 Webhook 与战报任务
|
# 如果配置了联控,启动 Webhook 与战报任务
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
||||||
@@ -362,8 +536,8 @@ if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
|||||||
|
|
||||||
# [v3.0.1新增修改 3: 删除原来的 curl 取 IP,直接使用我们上方锁定的 BIND_IP]
|
# [v3.0.1新增修改 3: 删除原来的 curl 取 IP,直接使用我们上方锁定的 BIND_IP]
|
||||||
# 并提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的重复推送
|
# 并提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的重复推送
|
||||||
# [修复竞态]: 提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的抢跑推送
|
# [修复竞态]: 提前写入公网 IP 缓存,彻底阻断 agent_daemon 首次启动时的抢跑推送
|
||||||
echo "$BIND_IP" > "${INSTALL_DIR}/core/.last_ip"
|
echo "$SAFE_PUBLIC_IP" > "${INSTALL_DIR}/core/.last_ip"
|
||||||
|
|
||||||
# 双保险守护进程看门狗
|
# 双保险守护进程看门狗
|
||||||
echo "@reboot nohup bash ${INSTALL_DIR}/core/agent_daemon.sh >/dev/null 2>&1 &" >> /tmp/cron_backup
|
echo "@reboot nohup bash ${INSTALL_DIR}/core/agent_daemon.sh >/dev/null 2>&1 &" >> /tmp/cron_backup
|
||||||
@@ -376,34 +550,83 @@ fi
|
|||||||
crontab /tmp/cron_backup
|
crontab /tmp/cron_backup
|
||||||
rm -f /tmp/cron_backup
|
rm -f /tmp/cron_backup
|
||||||
|
|
||||||
|
# ================== [v3.4.0 核心: 状态机驱动的热更新路由] ==================
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
||||||
echo -e "\n📡 正在向指挥部发送注册暗号..."
|
|
||||||
|
|
||||||
# 构造注册暗号 (V3.1.3 协议升级: 携带 REGION_CODE 大区标识)
|
|
||||||
NODE_NAME=$(hostname | cut -c 1-15)
|
|
||||||
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${BIND_IP}|${AGENT_PORT}"
|
|
||||||
|
|
||||||
# 执行主动推送
|
# [v3.5.2 核心] 发送携带双轨身份的注册指令 (追加 NODE_ALIAS 作为第 6 个字段)
|
||||||
PUSH_RESULT=$(curl -s -X POST "${TG_API_URL}" \
|
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${SAFE_PUBLIC_IP}|${AGENT_PORT}|${NODE_ALIAS}"
|
||||||
-d "chat_id=${CHAT_ID}" \
|
|
||||||
-d "parse_mode=Markdown" \
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
-d "text=✨ *IP-Sentinel 部署成功!*
|
# 读取本地老版本号,如果没有则视为远古版本 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
|
||||||
|
|
||||||
|
else
|
||||||
|
# [全新安装路由]
|
||||||
|
echo -e "\n📡 正在向指挥部发送注册暗号..."
|
||||||
|
PUSH_RESULT=$(curl -s -X POST "${TG_API_URL}" \
|
||||||
|
-d "chat_id=${CHAT_ID}" \
|
||||||
|
-d "parse_mode=Markdown" \
|
||||||
|
-d "text=✨ *IP-Sentinel 部署成功!*
|
||||||
📍 区域:${REGION_NAME}
|
📍 区域:${REGION_NAME}
|
||||||
🌐 IP:${BIND_IP}
|
🌐 IP:${SAFE_PUBLIC_IP}
|
||||||
🔌 端口:${AGENT_PORT}
|
🔌 端口:${AGENT_PORT}
|
||||||
|
|
||||||
🔑 *请点击下方指令复制并回复给机器人:*
|
🔑 *请点击下方指令复制并回复给机器人:*
|
||||||
\`${REG_MSG}\`")
|
\`${REG_MSG}\`")
|
||||||
|
|
||||||
if echo "$PUSH_RESULT" | grep -q '"ok":true'; then
|
if echo "$PUSH_RESULT" | grep -q '"ok":true'; then
|
||||||
echo -e "\033[32m✅ 注册信息已推送到您的 Telegram,请按指令完成最终激活!\033[0m"
|
echo -e "\033[32m✅ 注册信息已推送到您的 Telegram,请按指令完成最终激活!\033[0m"
|
||||||
else
|
else
|
||||||
echo -e "\033[31m❌ 消息推送失败,请检查 Chat ID 是否正确或是否已关注机器人。\033[0m"
|
echo -e "\033[31m❌ 消息推送失败,请检查 Chat ID 是否正确或是否已关注机器人。\033[0m"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
echo "🎉 边缘节点 (Agent) 部署流程彻底完成!"
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
|
echo "🎉 边缘节点 (Agent) 平滑热更新已彻底完成!"
|
||||||
|
else
|
||||||
|
echo "🎉 边缘节点 (Agent) 部署流程彻底完成!"
|
||||||
|
fi
|
||||||
echo "📍 你的本地守护区域已锁定为: $REGION_NAME"
|
echo "📍 你的本地守护区域已锁定为: $REGION_NAME"
|
||||||
echo "⚙️ 哨兵现已开启 [每30分钟] 的高频高拟真养护循环。"
|
echo "⚙️ 哨兵现已开启 [每30分钟] 的高频高拟真养护循环。"
|
||||||
if [[ -n "$TG_TOKEN" ]]; then
|
if [[ -n "$TG_TOKEN" ]]; then
|
||||||
@@ -416,7 +639,12 @@ if [[ -n "$TG_TOKEN" ]]; then
|
|||||||
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active firewalld | grep -qw active; 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"
|
FW_MSG="firewall-cmd --zone=public --add-port=$AGENT_PORT/tcp --permanent && firewall-cmd --reload"
|
||||||
elif command -v iptables >/dev/null 2>&1; then
|
elif command -v iptables >/dev/null 2>&1; then
|
||||||
FW_MSG="iptables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
|
# 智能双栈雷达:根据对外公网 IP 属性,动态下发对应的防火墙放行指令
|
||||||
|
if [[ "$SAFE_PUBLIC_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"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\033[33m⚠️ 警告:请务必确保本机及云服务商安全组放行了 TCP $AGENT_PORT 端口!\033[0m"
|
echo -e "\033[33m⚠️ 警告:请务必确保本机及云服务商安全组放行了 TCP $AGENT_PORT 端口!\033[0m"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: mod_google.sh (Google 业务逻辑模块)
|
# 脚本名称: mod_google.sh (Google 业务逻辑模块 - 动态锚点版)
|
||||||
# 核心功能: 执行坐标微抖动、模拟真实阅读时长、会话行为拉伸
|
# 核心功能: 执行坐标微抖动、模拟真实阅读时长、会话行为拉伸
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -16,11 +16,15 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback
|
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback (v3.4.0 引入版本探针)
|
||||||
if ! type log >/dev/null 2>&1; then
|
if ! type log >/dev/null 2>&1; then
|
||||||
log() {
|
log() {
|
||||||
|
# [v3.4.0 核心] 提取当前配置中的版本锚点
|
||||||
|
local local_ver="${AGENT_VERSION:-未知}"
|
||||||
|
|
||||||
mkdir -p "${INSTALL_DIR}/logs"
|
mkdir -p "${INSTALL_DIR}/logs"
|
||||||
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$2" "$1" "$REGION_CODE" "$3" >> "${INSTALL_DIR}/logs/sentinel.log"
|
# 统一日志格式,注入 [版本号] 追踪标识
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -48,8 +52,8 @@ get_random_coord() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- [环境初始化] ---
|
# --- [环境初始化] ---
|
||||||
# [v3.0.2修复] 直接读取系统已锁定的锚点 IP,彻底杜绝“获取IP失败”及隧道偏移
|
# [v3.3.1修改] 优先读取对外公网面孔作为哈希种子,兼容 NAT 机的空 BIND_IP
|
||||||
CURRENT_IP="${BIND_IP:-Unknown}"
|
CURRENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
|
||||||
|
|
||||||
# -----------------------------------------------------------
|
# -----------------------------------------------------------
|
||||||
# [V3.1.5] 哈希锚定法 (Hash-Seeded Persona)
|
# [V3.1.5] 哈希锚定法 (Hash-Seeded Persona)
|
||||||
@@ -85,6 +89,25 @@ log "$MODULE_NAME" "INFO " "当前出网 IP: $CURRENT_IP"
|
|||||||
log "$MODULE_NAME" "INFO " "设备指纹锁定: ${SESSION_UA:0:45}..."
|
log "$MODULE_NAME" "INFO " "设备指纹锁定: ${SESSION_UA:0:45}..."
|
||||||
log "$MODULE_NAME" "INFO " "虚拟驻留坐标: $SESSION_BASE_LAT, $SESSION_BASE_LON"
|
log "$MODULE_NAME" "INFO " "虚拟驻留坐标: $SESSION_BASE_LAT, $SESSION_BASE_LON"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
# [V3.2.1 热修复] 网络锚定与协议自适应构建
|
||||||
|
# 强制 curl 绑定网卡,并自动匹配 IPv4/v6 协议,杜绝 curl 冲突报错
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
CURL_BIND_OPT=""
|
||||||
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
||||||
|
|
||||||
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
|
# 智能探测:带冒号为 V6,带点号为 V4
|
||||||
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-6"
|
||||||
|
log "$MODULE_NAME" "INFO " "底层路由锁定: 绑定 IPv6 出口及协议 ($BIND_IP)"
|
||||||
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-4"
|
||||||
|
log "$MODULE_NAME" "INFO " "底层路由锁定: 绑定 IPv4 出口及协议 ($BIND_IP)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# --- [行为循环模拟] ---
|
# --- [行为循环模拟] ---
|
||||||
for ((i=1; i<=TOTAL_ACTIONS; i++)); do
|
for ((i=1; i<=TOTAL_ACTIONS; i++)); do
|
||||||
# 模拟真实移动设备拿在手里时的 GPS 信号微抖动 (范围约 10 米)
|
# 模拟真实移动设备拿在手里时的 GPS 信号微抖动 (范围约 10 米)
|
||||||
@@ -98,21 +121,22 @@ for ((i=1; i<=TOTAL_ACTIONS; i++)); do
|
|||||||
# 随机选择一种上网行为
|
# 随机选择一种上网行为
|
||||||
ACTION_TYPE=$((1 + RANDOM % 4))
|
ACTION_TYPE=$((1 + RANDOM % 4))
|
||||||
|
|
||||||
|
# [V3.2.1 热修复] 注入 $CURL_BIND_OPT 与 $DYNAMIC_IP_PREF 协议自适应
|
||||||
case $ACTION_TYPE in
|
case $ACTION_TYPE in
|
||||||
1) # 搜索行为
|
1) # 搜索行为
|
||||||
CODE=$(curl -${IP_PREF:-4} -m 15 -s -L -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
CODE=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -m 15 -s -L -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
||||||
"https://www.google.com/search?q=${ENCODED_KEY}&${LANG_PARAMS}")
|
"https://www.google.com/search?q=${ENCODED_KEY}&${LANG_PARAMS}")
|
||||||
;;
|
;;
|
||||||
2) # 浏览本土新闻
|
2) # 浏览本土新闻
|
||||||
CODE=$(curl -${IP_PREF:-4} -m 15 -s -L -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
CODE=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -m 15 -s -L -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
||||||
"https://news.google.com/home?${LANG_PARAMS}")
|
"https://news.google.com/home?${LANG_PARAMS}")
|
||||||
;;
|
;;
|
||||||
3) # 地图坐标查询
|
3) # 地图坐标查询
|
||||||
CODE=$(curl -${IP_PREF:-4} -m 15 -s -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
CODE=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -m 15 -s -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
||||||
"https://www.google.com/maps/search/$${ENCODED_KEY}/@${ACTION_LAT},${ACTION_LON},17z?${LANG_PARAMS}")
|
"https://www.google.com/maps/search/$${ENCODED_KEY}/@${ACTION_LAT},${ACTION_LON},17z?${LANG_PARAMS}")
|
||||||
;;
|
;;
|
||||||
4) # 触发移动端系统底层位置检测像素
|
4) # 触发移动端系统底层位置检测像素
|
||||||
CODE=$(curl -${IP_PREF:-4} -m 10 -s -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
CODE=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -m 10 -s -o /dev/null -w "%{http_code}" -A "$SESSION_UA" \
|
||||||
"https://connectivitycheck.gstatic.com/generate_204")
|
"https://connectivitycheck.gstatic.com/generate_204")
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -128,33 +152,45 @@ for ((i=1; i<=TOTAL_ACTIONS; i++)); do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- [结果纠偏自检 (V3.1.4 绝对精准提取版)] ---
|
# --- [结果纠偏自检 (V3.2.1 高精度容错版)] ---
|
||||||
FINAL_URL=$(curl -${IP_PREF:-4} -m 15 -s -L -o /dev/null -w "%{url_effective}" https://www.google.com)
|
# [V3.2.1 热修复] 探针同样应用 $DYNAMIC_IP_PREF 协议自适应
|
||||||
|
PROBE_RESULT=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -m 15 -s -L -o /dev/null -w "%{http_code}|%{url_effective}" https://www.google.com)
|
||||||
|
|
||||||
# 核心战术:利用 awk 精准提取最终 URL 的域名部分,再剔除 "www.google." 前缀,得到纯粹的后缀
|
# 分离状态码与 URL
|
||||||
# 例如: https://www.google.com.hk/?... -> 提取为 "com.hk"
|
PROBE_CODE=$(echo "$PROBE_RESULT" | cut -d'|' -f1)
|
||||||
ACTUAL_DOMAIN=$(echo "$FINAL_URL" | awk -F/ '{print $3}')
|
FINAL_URL=$(echo "$PROBE_RESULT" | cut -d'|' -f2)
|
||||||
ACTUAL_SUFFIX=${ACTUAL_DOMAIN#www.google.}
|
|
||||||
|
|
||||||
# 1. 优先验证:绝对匹配目标后缀 (彻底杜绝 com 包含于 com.hk 的陷阱)
|
# 0. 致命拦截:网络断开、DNS 解析失败或严重超时
|
||||||
if [ "$ACTUAL_SUFFIX" == "$VALID_URL_SUFFIX" ]; then
|
if [ "$PROBE_CODE" == "000" ] || [ -z "$FINAL_URL" ]; then
|
||||||
STATUS="✅ 目标区域达成 ($ACTUAL_SUFFIX)"
|
STATUS="🚨 探针失效 (网络阻断或底层路由异常)"
|
||||||
|
|
||||||
# 2. 核心拦截:精准捕捉送中特征 (com.hk)
|
|
||||||
elif [ "$ACTUAL_SUFFIX" == "com.hk" ]; then
|
|
||||||
if [ "$REGION_CODE" == "HK" ]; then
|
|
||||||
STATUS="✅ 目标区域达成 (HK 专属 com.hk)"
|
|
||||||
else
|
|
||||||
STATUS="❌ 严重漂移!判定为送中区 (实际跳往 $ACTUAL_SUFFIX)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3. 宽容处理:遵守 Google 无跳转新规 (严格限定必须是纯粹的 com,绝不能是 com.xx)
|
|
||||||
elif [ "$ACTUAL_SUFFIX" == "com" ]; then
|
|
||||||
STATUS="🌐 保持通用主站 (留在 .com,受 Google 无跳转新规影响)"
|
|
||||||
|
|
||||||
# 4. 跨区漂移:所有预判之外的后缀,全部视为异常
|
|
||||||
else
|
else
|
||||||
STATUS="⚠️ 跨区跳板漂移 (当前实际归属: $ACTUAL_SUFFIX)"
|
# 核心战术:精准提取最终 URL 的域名部分
|
||||||
|
ACTUAL_DOMAIN=$(echo "$FINAL_URL" | awk -F/ '{print $3}')
|
||||||
|
|
||||||
|
# [V3.2.1 优化] 使用通配符 * 剔除任意前缀 (无论是 www.google. 还是 ipv4.google.)
|
||||||
|
ACTUAL_SUFFIX=${ACTUAL_DOMAIN#*google.}
|
||||||
|
|
||||||
|
# 1. 优先验证:绝对匹配目标后缀 (彻底杜绝 com 包含于 com.hk 的陷阱)
|
||||||
|
if [ "$ACTUAL_SUFFIX" == "$VALID_URL_SUFFIX" ]; then
|
||||||
|
STATUS="✅ 目标区域达成 ($ACTUAL_SUFFIX)"
|
||||||
|
|
||||||
|
# 2. 核心拦截:精准捕捉送中特征 (com.hk)
|
||||||
|
elif [ "$ACTUAL_SUFFIX" == "com.hk" ]; then
|
||||||
|
if [ "$REGION_CODE" == "HK" ]; then
|
||||||
|
STATUS="✅ 目标区域达成 (HK 专属 com.hk)"
|
||||||
|
else
|
||||||
|
STATUS="❌ 严重漂移!判定为送中区 (实际跳往 $ACTUAL_SUFFIX)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 宽容处理:遵守 Google 无跳转新规 (严格限定必须是纯粹的 com)
|
||||||
|
# [视觉优化] 留在 .com 代表 IP 极度纯净未被区域沙盒锁定,计入成功战绩!
|
||||||
|
elif [ "$ACTUAL_SUFFIX" == "com" ]; then
|
||||||
|
STATUS="✅ 目标区域达成 (免签停留 .com 通用主站)"
|
||||||
|
|
||||||
|
# 4. 跨区漂移:所有预判之外的后缀,全部视为异常
|
||||||
|
else
|
||||||
|
STATUS="⚠️ 跨区跳板漂移 (当前实际归属: $ACTUAL_SUFFIX)"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "$MODULE_NAME" "SCORE" "自检结论: $STATUS"
|
log "$MODULE_NAME" "SCORE" "自检结论: $STATUS"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: mod_trust.sh (IP 信用净化模块 V3.1.4 拓扑自适应版)
|
# 脚本名称: mod_trust.sh (IP 信用净化模块 - 动态锚点版)
|
||||||
# 核心功能: 动态扫描本地 LBS 冷数据,提取权威白名单,执行流量净化
|
# 核心功能: 动态扫描本地 LBS 冷数据,提取权威白名单,执行流量净化
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -41,12 +41,16 @@ if [ ${#TRUST_URLS[@]} -eq 0 ]; then
|
|||||||
TRUST_URLS=("https://en.wikipedia.org/wiki/Special:Random" "https://www.apple.com/" "https://www.microsoft.com/")
|
TRUST_URLS=("https://en.wikipedia.org/wiki/Special:Random" "https://www.apple.com/" "https://www.microsoft.com/")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. 日志规范化
|
# 3. 日志规范化 (v3.4.0 引入版本探针)
|
||||||
log_msg() {
|
log_msg() {
|
||||||
local TYPE=$1
|
local TYPE=$1
|
||||||
local MSG=$2
|
local MSG=$2
|
||||||
local TIME=$(date "+%Y-%m-%d %H:%M:%S")
|
local TIME=$(date "+%Y-%m-%d %H:%M:%S")
|
||||||
echo "[$TIME] [$TYPE] [Trust ] [$REGION] $MSG" | tee -a "$LOG_FILE"
|
# [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"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 4. 锁定单次会话指纹
|
# 4. 锁定单次会话指纹
|
||||||
@@ -59,8 +63,8 @@ if [ -f "$UA_FILE" ]; then
|
|||||||
TOTAL_UA=${#UA_POOL[@]}
|
TOTAL_UA=${#UA_POOL[@]}
|
||||||
|
|
||||||
if [ "$TOTAL_UA" -gt 0 ]; then
|
if [ "$TOTAL_UA" -gt 0 ]; then
|
||||||
# 以本地锁定的公网 IP (BIND_IP) 为种子计算 CRC32 哈希值
|
# [v3.3.1修改] 优先使用固化的公网 IP 作为哈希种子,防止 NAT 节点指纹同质化
|
||||||
SEED=$(echo -n "${BIND_IP:-127.0.0.1}" | cksum | awk '{print $1}')
|
SEED=$(echo -n "${PUBLIC_IP:-${BIND_IP:-127.0.0.1}}" | cksum | awk '{print $1}')
|
||||||
|
|
||||||
# 利用确定的种子,在全球 4000 的库中,计算出本机的 3 个绝对专属坐标
|
# 利用确定的种子,在全球 4000 的库中,计算出本机的 3 个绝对专属坐标
|
||||||
IDX1=$(( SEED % TOTAL_UA ))
|
IDX1=$(( SEED % TOTAL_UA ))
|
||||||
@@ -87,6 +91,25 @@ log_msg "START" "========== 启动区域 IP 信用净化会话 =========="
|
|||||||
log_msg "INFO " "已载入 [${REGION}] 区域白名单,配置库条目: ${#TRUST_URLS[@]} 个"
|
log_msg "INFO " "已载入 [${REGION}] 区域白名单,配置库条目: ${#TRUST_URLS[@]} 个"
|
||||||
log_msg "INFO " "已锁定本地伪装指纹: $(echo $CURRENT_UA | cut -d' ' -f1-2)..."
|
log_msg "INFO " "已锁定本地伪装指纹: $(echo $CURRENT_UA | cut -d' ' -f1-2)..."
|
||||||
|
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
# [V3.2.1 热修复] 网络锚定与协议自适应构建
|
||||||
|
# 强制 curl 绑定网卡,并自动匹配 IPv4/v6 协议,杜绝 curl 冲突报错
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
CURL_BIND_OPT=""
|
||||||
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
||||||
|
|
||||||
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
|
# 智能探测:带冒号为 V6,带点号为 V4
|
||||||
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-6"
|
||||||
|
log_msg "INFO " "底层路由锁定: 绑定 IPv6 出口及协议 ($BIND_IP)"
|
||||||
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-4"
|
||||||
|
log_msg "INFO " "底层路由锁定: 绑定 IPv4 出口及协议 ($BIND_IP)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
STEP_COUNT=$((RANDOM % 4 + 3))
|
STEP_COUNT=$((RANDOM % 4 + 3))
|
||||||
SUCCESS_INJECT=0
|
SUCCESS_INJECT=0
|
||||||
|
|
||||||
@@ -95,7 +118,8 @@ for ((i=1; i<=STEP_COUNT; i++)); do
|
|||||||
TARGET_URL=${TRUST_URLS[$RANDOM % ${#TRUST_URLS[@]}]}
|
TARGET_URL=${TRUST_URLS[$RANDOM % ${#TRUST_URLS[@]}]}
|
||||||
|
|
||||||
# [v3.0.1修复] 注入高权重流量时,强制从绑定的 IPv4 或 IPv6 隧道出网
|
# [v3.0.1修复] 注入高权重流量时,强制从绑定的 IPv4 或 IPv6 隧道出网
|
||||||
HTTP_CODE=$(curl -${IP_PREF:-4} -A "$CURRENT_UA" \
|
# [V3.2.1 热修复] 注入 $CURL_BIND_OPT 与 $DYNAMIC_IP_PREF 协议自适应
|
||||||
|
HTTP_CODE=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -A "$CURRENT_UA" \
|
||||||
-H "Accept: text/html,application/xhtml+xml;q=0.9,image/avif,image/webp,*/*;q=0.8" \
|
-H "Accept: text/html,application/xhtml+xml;q=0.9,image/avif,image/webp,*/*;q=0.8" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9" \
|
-H "Accept-Language: en-US,en;q=0.9" \
|
||||||
-H "Sec-Fetch-Dest: document" \
|
-H "Sec-Fetch-Dest: document" \
|
||||||
@@ -104,7 +128,8 @@ for ((i=1; i<=STEP_COUNT; i++)); do
|
|||||||
--compressed \
|
--compressed \
|
||||||
-s -o /dev/null -w "%{http_code}" -m 15 "$TARGET_URL")
|
-s -o /dev/null -w "%{http_code}" -m 15 "$TARGET_URL")
|
||||||
|
|
||||||
if [[ "$HTTP_CODE" =~ ^(200|301|302)$ ]]; then
|
# 扩大 HTTP 状态码容错区间:包含所有 20x (如亚马逊的 202) 和 30x 重定向
|
||||||
|
if [[ "$HTTP_CODE" =~ ^(20[0-9]|30[1-8])$ ]]; then
|
||||||
log_msg "EXEC " "动作[$i/$STEP_COUNT]完成 | 状态: $HTTP_CODE | 注入: $TARGET_URL"
|
log_msg "EXEC " "动作[$i/$STEP_COUNT]完成 | 状态: $HTTP_CODE | 注入: $TARGET_URL"
|
||||||
((SUCCESS_INJECT++))
|
((SUCCESS_INJECT++))
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 V2.0 智能分配版)
|
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 - 动态锚点版)
|
||||||
# 核心功能: 防并发延迟启动、功能开关(Feature Flag)自适应、多模块概率轮盘调度
|
# 核心功能: 防并发延迟启动、功能开关(Feature Flag)自适应、多模块概率轮盘调度
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -15,14 +15,18 @@ if [ ! -f "$CONFIG_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
# 2. 全局日志写入函数 (导出给子进程共享使用)
|
# 2. 全局日志写入函数 (导出给子进程共享使用,v3.4.0 引入版本探针)
|
||||||
log() {
|
log() {
|
||||||
local module=$1
|
local module=$1
|
||||||
local level=$2
|
local level=$2
|
||||||
local msg=$3
|
local msg=$3
|
||||||
|
# [v3.4.0 核心] 提取当前配置中的版本锚点
|
||||||
|
local local_ver="${AGENT_VERSION:-未知}"
|
||||||
|
|
||||||
# 保证日志目录存在
|
# 保证日志目录存在
|
||||||
mkdir -p "${INSTALL_DIR}/logs"
|
mkdir -p "${INSTALL_DIR}/logs"
|
||||||
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$level" "$module" "$REGION_CODE" "$msg" >> "$LOG_FILE"
|
# 日志格式注入 [版本号] 追踪标识
|
||||||
|
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [v%-5s] [%-5s] [%-7s] [%s] %s\n" "$local_ver" "$level" "$module" "$REGION_CODE" "$msg" >> "$LOG_FILE"
|
||||||
}
|
}
|
||||||
export -f log
|
export -f log
|
||||||
export CONFIG_FILE INSTALL_DIR
|
export CONFIG_FILE INSTALL_DIR
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: tg_daemon.sh (Telegram 互动监听守护进程)
|
# 脚本名称: tg_daemon.sh (Telegram 互动监听守护进程 - 动态锚点版)
|
||||||
# 核心功能: 极低功耗长轮询监听 TG 指令,实现远程控制
|
# 核心功能: 极低功耗长轮询监听、节点溯源、版本继承
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
@@ -16,6 +16,11 @@ source "$CONFIG_FILE"
|
|||||||
# 如果没有配置 TG 机器人,则安静退出守护进程
|
# 如果没有配置 TG 机器人,则安静退出守护进程
|
||||||
[ -z "$TG_TOKEN" ] || [ -z "$CHAT_ID" ] && exit 0
|
[ -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) 记录文件,防止重启后重复处理老消息
|
# 2. 初始化消息偏移量 (Offset) 记录文件,防止重启后重复处理老消息
|
||||||
OFFSET=0
|
OFFSET=0
|
||||||
[ -f "$OFFSET_FILE" ] && OFFSET=$(cat "$OFFSET_FILE")
|
[ -f "$OFFSET_FILE" ] && OFFSET=$(cat "$OFFSET_FILE")
|
||||||
@@ -44,20 +49,20 @@ while true; do
|
|||||||
if [ "$MSG_CHAT_ID" == "$CHAT_ID" ]; then
|
if [ "$MSG_CHAT_ID" == "$CHAT_ID" ]; then
|
||||||
case "$MSG_TEXT" in
|
case "$MSG_TEXT" in
|
||||||
"/run")
|
"/run")
|
||||||
send_msg "🚀 **[指令下达]** 正在后台立即触发 IP 养护任务..."
|
send_msg "🚀 **[${NODE_NAME}]** 正在后台触发 IP 养护任务 (v${LOCAL_VER})..."
|
||||||
# 使用 nohup 另起后台独立进程运行,防止阻塞当前监听器的循环
|
# 使用 nohup 另起后台独立进程运行,防止阻塞当前监听器的循环
|
||||||
nohup bash "${INSTALL_DIR}/core/mod_google.sh" >/dev/null 2>&1 &
|
nohup bash "${INSTALL_DIR}/core/mod_google.sh" >/dev/null 2>&1 &
|
||||||
;;
|
;;
|
||||||
"/log")
|
"/log")
|
||||||
LOG_DATA=$(tail -n 15 "${INSTALL_DIR}/logs/sentinel.log")
|
LOG_DATA=$(tail -n 15 "${INSTALL_DIR}/logs/sentinel.log")
|
||||||
send_msg "📄 **[最近 15 行系统日志]**%0A\`\`\`log%0A${LOG_DATA}%0A\`\`\`"
|
send_msg "📄 **[${NODE_NAME}] 实时日志 (v${LOCAL_VER}):**%0A\`\`\`log%0A${LOG_DATA}%0A\`\`\`"
|
||||||
;;
|
;;
|
||||||
"/report")
|
"/report")
|
||||||
# 触发生成一次战报
|
# 触发生成一次战报
|
||||||
bash "${INSTALL_DIR}/core/tg_report.sh"
|
bash "${INSTALL_DIR}/core/tg_report.sh"
|
||||||
;;
|
;;
|
||||||
"/help"|"/start")
|
"/help"|"/start")
|
||||||
HELP_MSG="🛡️ **IP-Sentinel 控制台**%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
|
HELP_MSG="🛡️ **IP-Sentinel 边缘控制台**%0A📍 节点: \`${NODE_NAME}\`%0A🔖 版本: \`v${LOCAL_VER}\`%0A%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
|
||||||
send_msg "$HELP_MSG"
|
send_msg "$HELP_MSG"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: tg_report.sh (Telegram 每日战报模块 V6.0 动态拼装版)
|
# 脚本名称: tg_report.sh (Telegram 每日战报模块 - 动态锚点版)
|
||||||
# 核心功能: 适配 Feature Flag 架构,按需展示 Google/Trust 独立统计数据
|
# 核心功能: 适配 Feature Flag 架构,按需展示独立统计数据,OTA 更新预警
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
@@ -18,19 +18,58 @@ if [ -z "$TG_TOKEN" ] || [ -z "$CHAT_ID" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. 节点元数据抓取 (v3.0.1修复: 严格使用配置中的协议探测出口与多节点容灾)
|
# 2. 节点元数据抓取 (v3.2.2 协议自适应与多级容灾版)
|
||||||
NODE_NAME=$(hostname | cut -c 1-15)
|
# [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}"
|
||||||
|
|
||||||
# 多节点容灾探测
|
# --- [防线 1: 底层路由锁定与协议自适应] ---
|
||||||
CURRENT_IP=$( (curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip || curl -${IP_PREF:-4} -s -m 5 ifconfig.me) 2>/dev/null | tr -d '[:space:]' )
|
CURL_BIND_OPT=""
|
||||||
# 强制兜底:如果所有外部 API 都挂了,直接使用本地强行锁定的 BIND_IP
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}"
|
||||||
[ -z "$CURRENT_IP" ] && CURRENT_IP="$BIND_IP"
|
|
||||||
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-6"
|
||||||
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
|
DYNAMIC_IP_PREF="-4"
|
||||||
|
fi
|
||||||
|
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}"
|
||||||
|
|
||||||
# 为可能获取到的 IPv6 自动添加方括号护甲
|
# 为可能获取到的 IPv6 自动添加方括号护甲
|
||||||
[[ "$CURRENT_IP" == *":"* ]] && [[ "$CURRENT_IP" != *"["* ]] && CURRENT_IP="[${CURRENT_IP}]"
|
[[ "$CURRENT_IP" == *":"* ]] && [[ "$CURRENT_IP" != *"["* ]] && CURRENT_IP="[${CURRENT_IP}]"
|
||||||
|
|
||||||
# 智能判断 IP 属性
|
# --- [防线 2: 多级 ISP 容灾探针链路] ---
|
||||||
ISP_INFO=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/geoip | jq -r '.organization' 2>/dev/null)
|
ISP_INFO=""
|
||||||
|
|
||||||
|
# 探针 A: 纯文本 API (免 jq,极速稳定)
|
||||||
|
ISP_INFO=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -s -m 5 ipinfo.io/org 2>/dev/null)
|
||||||
|
|
||||||
|
# 探针 B: 备用纯文本 API
|
||||||
|
if [ -z "$ISP_INFO" ] || [[ "$ISP_INFO" == *"error"* ]]; then
|
||||||
|
ISP_INFO=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -s -m 5 ip-api.com/line/?fields=isp 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 探针 C: 原版的 JSON API (需要 jq 兜底)
|
||||||
|
if [ -z "$ISP_INFO" ] || [[ "$ISP_INFO" == *"error"* ]]; then
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
ISP_INFO=$(curl $CURL_BIND_OPT $DYNAMIC_IP_PREF -s -m 5 api.ip.sb/geoip | jq -r '.organization' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- [防线 3: 数据清洗 (遵循底层共识原则)] ---
|
||||||
|
# 剔除 ipinfo 返回的开头 AS 号 (例如 "AS137535 JT TELECOM" -> "JT TELECOM")
|
||||||
|
ISP_INFO=$(echo "$ISP_INFO" | sed -E 's/^AS[0-9]+ //')
|
||||||
|
|
||||||
|
# 最终兜底判断
|
||||||
[ -z "$ISP_INFO" ] || [ "$ISP_INFO" == "null" ] && ISP_INFO="未知 ISP"
|
[ -z "$ISP_INFO" ] || [ "$ISP_INFO" == "null" ] && ISP_INFO="未知 ISP"
|
||||||
|
|
||||||
if [[ "$ISP_INFO" == *"Cloudflare"* ]]; then
|
if [[ "$ISP_INFO" == *"Cloudflare"* ]]; then
|
||||||
@@ -57,7 +96,7 @@ if [ -z "$LOG_CONTENT" ]; then
|
|||||||
read -r -d '' MSG <<EOT
|
read -r -d '' MSG <<EOT
|
||||||
🛑 **[IP-Sentinel] 告警:节点异常**
|
🛑 **[IP-Sentinel] 告警:节点异常**
|
||||||
----------------------------
|
----------------------------
|
||||||
📍 **节点名称**: \`${NODE_NAME}\`
|
📍 **节点名称**: \`${NODE_ALIAS}\`
|
||||||
⚠️ **警告**: 过去 24 小时无运行日志!
|
⚠️ **警告**: 过去 24 小时无运行日志!
|
||||||
🛠️ **建议**: 节点可能刚部署完毕,请在面板手动执行一次养护动作。
|
🛠️ **建议**: 节点可能刚部署完毕,请在面板手动执行一次养护动作。
|
||||||
EOT
|
EOT
|
||||||
@@ -75,7 +114,7 @@ else
|
|||||||
# 开始组装战报头部
|
# 开始组装战报头部
|
||||||
MSG="📊 **IP-Sentinel 每日简报 (${FLAG} ${REGION_NAME})**
|
MSG="📊 **IP-Sentinel 每日简报 (${FLAG} ${REGION_NAME})**
|
||||||
----------------------------
|
----------------------------
|
||||||
📍 **节点名称**: \`${NODE_NAME}\`
|
📍 **节点名称**: \`${NODE_ALIAS}\`
|
||||||
📡 **出口 IP**: \`${CURRENT_IP}\`
|
📡 **出口 IP**: \`${CURRENT_IP}\`
|
||||||
🛡️ **IP 属性**: ${IP_TYPE}"
|
🛡️ **IP 属性**: ${IP_TYPE}"
|
||||||
|
|
||||||
@@ -119,12 +158,41 @@ else
|
|||||||
|
|
||||||
🕒 **最近执行快照 [${LAST_MOD:-"System"}]:**
|
🕒 **最近执行快照 [${LAST_MOD:-"System"}]:**
|
||||||
时间: ${LAST_TIME:-"暂无数据"}
|
时间: ${LAST_TIME:-"暂无数据"}
|
||||||
结论: ${LAST_SCORE:-"暂无数据"}
|
结论: ${LAST_SCORE:-"暂无数据"}"
|
||||||
----------------------------
|
|
||||||
💡 哨兵正在后台默默守护您的资产。"
|
|
||||||
|
|
||||||
fi
|
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
|
||||||
|
----------------------------
|
||||||
|
🛡️ **系统引擎状态**
|
||||||
|
当前运行版本: \`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 推送 (接入安全网关)
|
# 5. 调用 API 推送 (接入安全网关)
|
||||||
RESPONSE=$(curl -s -m 10 -X POST "${TG_API_URL}" \
|
RESPONSE=$(curl -s -m 10 -X POST "${TG_API_URL}" \
|
||||||
-d "chat_id=${CHAT_ID}" \
|
-d "chat_id=${CHAT_ID}" \
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 V3.1.4 焦土版)
|
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 - 动态锚点版)
|
||||||
# 核心功能: 无痕清理守护进程、定时任务、运行目录及临时缓存
|
# 核心功能: 无痕清理守护进程、定时任务、运行目录及临时缓存
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -9,6 +9,13 @@ INSTALL_DIR="/opt/ip_sentinel"
|
|||||||
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
echo " 🗑️ 准备卸载 IP-Sentinel (边缘节点 Edge Agent)"
|
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 "========================================================"
|
echo "========================================================"
|
||||||
|
|
||||||
# 1. 停止运行中的守护进程与主控模块 (涵盖所有历史版本进程)
|
# 1. 停止运行中的守护进程与主控模块 (涵盖所有历史版本进程)
|
||||||
@@ -17,6 +24,8 @@ echo "[1/3] 正在终止后台守护进程与所有养护任务..."
|
|||||||
# 使用 pkill 替代传统的 pgrep | xargs,指令更短、容错率更高
|
# 使用 pkill 替代传统的 pgrep | xargs,指令更短、容错率更高
|
||||||
pkill -9 -f "tg_daemon.sh" >/dev/null 2>&1
|
pkill -9 -f "tg_daemon.sh" >/dev/null 2>&1
|
||||||
pkill -9 -f "agent_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 "webhook.py" >/dev/null 2>&1
|
||||||
pkill -9 -f "runner.sh" >/dev/null 2>&1
|
pkill -9 -f "runner.sh" >/dev/null 2>&1
|
||||||
pkill -9 -f "updater.sh" >/dev/null 2>&1
|
pkill -9 -f "updater.sh" >/dev/null 2>&1
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: updater.sh (IP-Sentinel V3.1.4 养料注入与维护中枢)
|
# 脚本名称: updater.sh (IP-Sentinel 养料注入与分频调度中枢 - 动态锚点版)
|
||||||
# 核心功能: 静默更新热数据(指纹/词库/LBS规则)、清理瘦身日志
|
# 核心功能: 静默更新热数据/LBS、指纹库错峰调度、强制出站死锁、版本无缝继承
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
CONFIG_FILE="${INSTALL_DIR}/config.conf"
|
CONFIG_FILE="${INSTALL_DIR}/config.conf"
|
||||||
# 你的 GitHub 仓库 Raw 数据直链前缀 (统一标准)
|
UA_TIME_FILE="${INSTALL_DIR}/core/.ua_last_update"
|
||||||
|
|
||||||
|
# GitHub 仓库 Raw 数据直链前缀
|
||||||
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
||||||
# 临时改为私库地址用于测试
|
|
||||||
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
|
|
||||||
|
|
||||||
# 1. 加载本地冷数据配置
|
# 1. 加载本地冷数据配置
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||||||
@@ -18,57 +18,106 @@ if [ ! -f "$CONFIG_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
# 2. 全局日志写入函数
|
# 2. 全局日志写入函数 (v3.4.0 引入版本探针)
|
||||||
log() {
|
log() {
|
||||||
|
# [v3.4.0 核心] 提取当前配置中的版本锚点
|
||||||
|
local local_ver="${AGENT_VERSION:-未知}"
|
||||||
|
|
||||||
mkdir -p "${INSTALL_DIR}/logs"
|
mkdir -p "${INSTALL_DIR}/logs"
|
||||||
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [%-5s] [%-7s] [%s] %s\n" "$2" "$1" "$REGION_CODE" "$3" >> "$LOG_FILE"
|
# 日志格式注入 [版本号] 追踪标识
|
||||||
|
printf "[$(date '+%Y-%m-%d %H:%M:%S')] [v%-5s] [%-5s] [%-7s] [%s] %s\n" "$local_ver" "$2" "$1" "$REGION_CODE" "$3" >> "$LOG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
log "Updater" "INFO " "========== 触发后台静默 OTA 热数据更新 =========="
|
log "Updater" "INFO " "========== 触发后台静默 OTA 热数据更新 =========="
|
||||||
|
|
||||||
# 3. 容灾机制拉取 UA 指纹池 (强制遵循锚点协议)
|
# ==========================================================
|
||||||
TMP_UA="/tmp/ip_sentinel_ua.txt"
|
# 🛡️ 终极护城河:构建强锚定出站的 curl 请求引擎
|
||||||
curl -${IP_PREF:-4} -sL "${REPO_RAW_URL}/data/user_agents.txt" -o "$TMP_UA"
|
# ==========================================================
|
||||||
if [ -s "$TMP_UA" ]; then
|
# 基础参数:跟随 install.sh 锁定的协议偏好 (4 或 6)
|
||||||
mv "$TMP_UA" "${INSTALL_DIR}/data/user_agents.txt"
|
CURL_CMD="curl -${IP_PREF:-4} -sL"
|
||||||
log "Updater" "INFO " "✅ 设备指纹池 (User-Agents) 更新成功"
|
|
||||||
else
|
# 【防坑核心】如果用户配置了死锁锚点,必须强制绑定网卡,杜绝流量溢出!
|
||||||
log "Updater" "WARN " "❌ UA 池拉取失败,保留本地旧数据防崩溃"
|
if [ -n "$BIND_IP" ]; then
|
||||||
rm -f "$TMP_UA"
|
# curl 的 --interface 参数不支持带方括号的 IPv6 地址,必须强行脱壳
|
||||||
|
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
||||||
|
CURL_CMD="$CURL_CMD --interface $RAW_BIND_IP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. 容灾机制拉取当地最新搜索词库
|
# ==========================================================
|
||||||
|
# 3. 容灾机制拉取 UA 指纹池 (V3.3.0 引入 30 天错峰防惊群逻辑)
|
||||||
|
# ==========================================================
|
||||||
|
NOW=$(date +%s)
|
||||||
|
LAST_UPDATE=0
|
||||||
|
|
||||||
|
# 读取上一次更新的时间戳
|
||||||
|
if [ -f "$UA_TIME_FILE" ]; then
|
||||||
|
# tr -d 清除可能存在的换行或回车符,防止算术崩溃
|
||||||
|
LAST_UPDATE=$(cat "$UA_TIME_FILE" | tr -d '\r\n')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 校验数据合法性,防崩溃
|
||||||
|
if ! [[ "$LAST_UPDATE" =~ ^[0-9]+$ ]]; then
|
||||||
|
LAST_UPDATE=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
DIFF=$((NOW - LAST_UPDATE))
|
||||||
|
|
||||||
|
# 距离上次拉取超过 30 天 (2592000 秒),才执行下载
|
||||||
|
if [ "$DIFF" -ge 2592000 ] || [ "$LAST_UPDATE" -eq 0 ]; then
|
||||||
|
TMP_UA="/tmp/ip_sentinel_ua.txt"
|
||||||
|
# 使用重装升级后的 CURL_CMD
|
||||||
|
$CURL_CMD "${REPO_RAW_URL}/data/user_agents.txt" -o "$TMP_UA"
|
||||||
|
|
||||||
|
if [ -s "$TMP_UA" ]; then
|
||||||
|
mv "$TMP_UA" "${INSTALL_DIR}/data/user_agents.txt"
|
||||||
|
echo "$NOW" > "$UA_TIME_FILE"
|
||||||
|
log "Updater" "INFO " "✅ 设备指纹池 (User-Agents) 30天错峰滚动更新成功"
|
||||||
|
else
|
||||||
|
log "Updater" "WARN " "❌ UA 池拉取失败,保留本地旧数据防崩溃"
|
||||||
|
rm -f "$TMP_UA"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
DAYS_LEFT=$(((2592000 - DIFF) / 86400))
|
||||||
|
log "Updater" "INFO " "⏳ 设备指纹池处于 30 天静默期 (剩余约 ${DAYS_LEFT} 天),跳过拉取"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# 4. 容灾机制拉取当地最新搜索词库 (每日高频拉取,保证活体新鲜度)
|
||||||
|
# ==========================================================
|
||||||
TMP_KW="/tmp/ip_sentinel_kw.txt"
|
TMP_KW="/tmp/ip_sentinel_kw.txt"
|
||||||
curl -${IP_PREF:-4} -sL "${REPO_RAW_URL}/data/keywords/kw_${REGION_CODE}.txt" -o "$TMP_KW"
|
$CURL_CMD "${REPO_RAW_URL}/data/keywords/kw_${REGION_CODE}.txt" -o "$TMP_KW"
|
||||||
|
|
||||||
if [ -s "$TMP_KW" ]; then
|
if [ -s "$TMP_KW" ]; then
|
||||||
mv "$TMP_KW" "${INSTALL_DIR}/data/keywords/kw_${REGION_CODE}.txt"
|
mv "$TMP_KW" "${INSTALL_DIR}/data/keywords/kw_${REGION_CODE}.txt"
|
||||||
log "Updater" "INFO " "✅ 区域搜索词库 (kw_${REGION_CODE}) 更新成功"
|
log "Updater" "INFO " "✅ 区域搜索词库 (kw_${REGION_CODE}) 每日同步成功"
|
||||||
else
|
else
|
||||||
log "Updater" "WARN " "❌ 搜索词库拉取失败,保留本地旧数据防崩溃"
|
log "Updater" "WARN " "❌ 搜索词库拉取失败,保留本地旧数据防崩溃"
|
||||||
rm -f "$TMP_KW"
|
rm -f "$TMP_KW"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. 【V3.1.4 核心升级】自适应拉取本地 LBS 专属 JSON 规则库
|
# ==========================================================
|
||||||
# 利用 find 穿透寻找本地唯一的 json
|
# 5. 自适应拉取本地 LBS 专属 JSON 规则库 (每日同步)
|
||||||
|
# ==========================================================
|
||||||
REGION_JSON_FILE=$(find "${INSTALL_DIR}/data/regions" -name "*.json" 2>/dev/null | head -n 1)
|
REGION_JSON_FILE=$(find "${INSTALL_DIR}/data/regions" -name "*.json" 2>/dev/null | head -n 1)
|
||||||
|
|
||||||
if [ -n "$REGION_JSON_FILE" ] && [ -f "$REGION_JSON_FILE" ]; then
|
if [ -n "$REGION_JSON_FILE" ] && [ -f "$REGION_JSON_FILE" ]; then
|
||||||
# 提取本地文件的相对路径 (例如: data/regions/US/CA/San_Jose.json)
|
|
||||||
REL_PATH=${REGION_JSON_FILE#*${INSTALL_DIR}/}
|
REL_PATH=${REGION_JSON_FILE#*${INSTALL_DIR}/}
|
||||||
TMP_JSON="/tmp/ip_sentinel_region.json"
|
TMP_JSON="/tmp/ip_sentinel_region.json"
|
||||||
|
|
||||||
# 按照相同的相对路径去云端拉取更新
|
$CURL_CMD "${REPO_RAW_URL}/${REL_PATH}" -o "$TMP_JSON"
|
||||||
curl -${IP_PREF:-4} -sL "${REPO_RAW_URL}/${REL_PATH}" -o "$TMP_JSON"
|
|
||||||
if [ -s "$TMP_JSON" ]; then
|
if [ -s "$TMP_JSON" ]; then
|
||||||
mv "$TMP_JSON" "$REGION_JSON_FILE"
|
mv "$TMP_JSON" "$REGION_JSON_FILE"
|
||||||
log "Updater" "INFO " "✅ 核心战区规则库 ($REL_PATH) 更新成功"
|
log "Updater" "INFO " "✅ 核心战区规则库 ($REL_PATH) 每日同步成功"
|
||||||
else
|
else
|
||||||
log "Updater" "WARN " "❌ 战区规则库拉取失败,保留本地旧数据"
|
log "Updater" "WARN " "❌ 战区规则库拉取失败,保留本地旧数据"
|
||||||
rm -f "$TMP_JSON"
|
rm -f "$TMP_JSON"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
# 6. 日志防满瘦身机制 (保留最近 2000 行)
|
# 6. 日志防满瘦身机制 (保留最近 2000 行)
|
||||||
|
# ==========================================================
|
||||||
if [ -f "$LOG_FILE" ]; then
|
if [ -f "$LOG_FILE" ]; then
|
||||||
tail -n 2000 "$LOG_FILE" > "${LOG_FILE}.tmp"
|
tail -n 2000 "$LOG_FILE" > "${LOG_FILE}.tmp"
|
||||||
mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
||||||
|
|||||||
45
data/keywords/kw_CA.txt
Normal file
45
data/keywords/kw_CA.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
@@ -1,3 +1,63 @@
|
|||||||
|
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
|
wetter frankfurt heute
|
||||||
bundesliga ergebnisse
|
bundesliga ergebnisse
|
||||||
aktuelle nachrichten deutschland
|
aktuelle nachrichten deutschland
|
||||||
|
|||||||
45
data/keywords/kw_ES.txt
Normal file
45
data/keywords/kw_ES.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
@@ -1,3 +1,62 @@
|
|||||||
|
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
|
meteo paris
|
||||||
actualités en direct
|
actualités en direct
|
||||||
résultats ligue 1
|
résultats ligue 1
|
||||||
|
|||||||
@@ -1,3 +1,62 @@
|
|||||||
|
歐聯
|
||||||
|
神戶勝利船
|
||||||
|
潘宏彬
|
||||||
|
姚正菁
|
||||||
|
木乃伊
|
||||||
|
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 港鐵路線圖
|
MTR 港鐵路線圖
|
||||||
OpenRice 附近美食
|
OpenRice 附近美食
|
||||||
|
|||||||
@@ -1,7 +1,64 @@
|
|||||||
|
小芝風花
|
||||||
|
中井亜美
|
||||||
|
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
|
||||||
|
サンディスク 株価
|
||||||
|
らじるらじる
|
||||||
|
マクドナルド
|
||||||
|
ロシア
|
||||||
|
広島市
|
||||||
|
ゲイブル・スティーブソン
|
||||||
|
日本維新の会
|
||||||
|
新 日本 繊維
|
||||||
|
高見沢 俊彦
|
||||||
|
不登校
|
||||||
|
後期高齢者医療制度
|
||||||
|
バーミヤン
|
||||||
|
宮澤エマ
|
||||||
|
チケプラ
|
||||||
|
横綱
|
||||||
|
宮里美香
|
||||||
東京 天気 明日
|
東京 天気 明日
|
||||||
新宿 おすすめ 居酒屋
|
新宿 おすすめ 居酒屋
|
||||||
最新のニュース 速報
|
最新のニュース 速報
|
||||||
ゴールド 相場 チャート
|
ゴールド 相場 チャート
|
||||||
近くの静かなカフェ
|
近くの静かなカフェ
|
||||||
地震 速報
|
円安 影響 生活
|
||||||
円安 影響 生活
|
|
||||||
|
|||||||
45
data/keywords/kw_NL.txt
Normal file
45
data/keywords/kw_NL.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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é
|
||||||
@@ -1,3 +1,63 @@
|
|||||||
|
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
|
singapore weather forecast
|
||||||
mrt map singapore
|
mrt map singapore
|
||||||
straitstimes breaking news
|
straitstimes breaking news
|
||||||
|
|||||||
25
data/keywords/kw_TW.txt
Normal file
25
data/keywords/kw_TW.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
歐聯
|
||||||
|
菡生婦幼診所
|
||||||
|
台鐵訂票
|
||||||
|
飛機
|
||||||
|
東光路
|
||||||
|
货币
|
||||||
|
amd
|
||||||
|
rklb
|
||||||
|
航空母艦
|
||||||
|
axti
|
||||||
|
Yahoo奇摩
|
||||||
|
天氣
|
||||||
|
蝦皮購物
|
||||||
|
PChome
|
||||||
|
Momo購物網
|
||||||
|
Mobile01
|
||||||
|
Dcard
|
||||||
|
巴哈姆特
|
||||||
|
中時電子報
|
||||||
|
聯合新聞網
|
||||||
|
台灣高鐵
|
||||||
|
台鐵時刻表
|
||||||
|
中華電信
|
||||||
|
統一發票
|
||||||
|
勞動部
|
||||||
@@ -1,3 +1,63 @@
|
|||||||
|
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
|
london weather today
|
||||||
bbc news latest
|
bbc news latest
|
||||||
premier league fixtures
|
premier league fixtures
|
||||||
|
|||||||
@@ -1,3 +1,61 @@
|
|||||||
|
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
|
Los Angeles weather today
|
||||||
S&P 500 stock chart
|
S&P 500 stock chart
|
||||||
local coffee shops near me
|
local coffee shops near me
|
||||||
@@ -7,3 +65,6 @@ AI startups in Silicon ValleySan Jose weather this weekend
|
|||||||
Silicon Valley tech news
|
Silicon Valley tech news
|
||||||
best tacos in San Jose
|
best tacos in San Jose
|
||||||
Apple park visitor center hours
|
Apple park visitor center hours
|
||||||
|
Seattle Weather
|
||||||
|
Las Vegas strip
|
||||||
|
Charlotte Hornets
|
||||||
|
|||||||
45
data/keywords/kw_VN.txt
Normal file
45
data/keywords/kw_VN.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
270
data/map.json
270
data/map.json
@@ -1,105 +1,183 @@
|
|||||||
{
|
{
|
||||||
"version": "3.1.0",
|
"version": "3.5.2",
|
||||||
"updated_at": "2026-04-11",
|
"updated_at": "2026-04-16",
|
||||||
"countries": [
|
"continents": [
|
||||||
{
|
{
|
||||||
"id": "US",
|
"id": "ASIA",
|
||||||
"name": "United States (美国)",
|
"name": "亚太战区 (Asia-Pacific)",
|
||||||
"keyword_file": "kw_US.txt",
|
"countries": [
|
||||||
"states": [
|
{
|
||||||
|
"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": "CA",
|
"id": "CA",
|
||||||
"name": "California (加州)",
|
"name": "Canada (加拿大)",
|
||||||
"cities": [
|
"keyword_file": "kw_CA.txt",
|
||||||
{ "id": "Los_Angeles", "name": "Los Angeles (洛杉矶)" },
|
"states": [
|
||||||
{ "id": "San_Jose", "name": "San Jose (圣何塞)" }
|
{
|
||||||
]
|
"id": "Default",
|
||||||
}
|
"name": "Default State",
|
||||||
]
|
"cities": [
|
||||||
},
|
{ "id": "Toronto", "name": "Toronto (多伦多)" },
|
||||||
{
|
{ "id": "Montreal", "name": "Montreal (蒙特利尔)" }
|
||||||
"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 (香港)" }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
17
data/regions/CA/Default/Montreal.json
Normal file
17
data/regions/CA/Default/Montreal.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
data/regions/CA/Default/Toronto.json
Normal file
20
data/regions/CA/Default/Toronto.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
data/regions/ES/Default/Madrid.json
Normal file
20
data/regions/ES/Default/Madrid.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
data/regions/NL/Default/Amsterdam.json
Normal file
20
data/regions/NL/Default/Amsterdam.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
data/regions/TW/Default/Taipei.json
Normal file
20
data/regions/TW/Default/Taipei.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
17
data/regions/UK/Default/Coventry.json
Normal file
17
data/regions/UK/Default/Coventry.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
data/regions/US/IL/Warrenville.json
Normal file
5
data/regions/US/IL/Warrenville.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
5
data/regions/US/NC/Charlotte.json
Normal file
5
data/regions/US/NC/Charlotte.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
5
data/regions/US/NV/Las_Vegas.json
Normal file
5
data/regions/US/NV/Las_Vegas.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
5
data/regions/US/OR/Bend.json
Normal file
5
data/regions/US/OR/Bend.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
5
data/regions/US/UT/Salt_Lake_City.json
Normal file
5
data/regions/US/UT/Salt_Lake_City.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
5
data/regions/US/WA/Seattle.json
Normal file
5
data/regions/US/WA/Seattle.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"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/" ] }
|
||||||
|
}
|
||||||
20
data/regions/VN/Default/Hanoi.json
Normal file
20
data/regions/VN/Default/Hanoi.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# [新增] 提取仓库直链前缀变量,方便后续在官方库和私库间一键切换
|
# ==========================================================
|
||||||
|
# 脚本名称: install_master.sh (IP-Sentinel 控制中枢部署脚本 - 动态锚点版)
|
||||||
|
# 核心功能: 部署/卸载调度中枢、SQLite 资产管理、平滑热更新引擎
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
# 你的 GitHub 仓库 Raw 数据直链前缀
|
||||||
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
||||||
# 临时改为私库地址用于测试
|
# 临时改为私库地址用于测试
|
||||||
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
|
# 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"
|
MASTER_DIR="/opt/ip_sentinel_master"
|
||||||
DB_FILE="${MASTER_DIR}/sentinel.db"
|
DB_FILE="${MASTER_DIR}/sentinel.db"
|
||||||
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
# [修改] 将欢迎语改为更通用的文案,因为现在不仅能部署,还能卸载
|
# [修改] 将欢迎语改为更通用的文案,因为现在不仅能部署,还能卸载
|
||||||
echo " 🧠 欢迎使用 IP-Sentinel Master (控制中枢)"
|
echo " 🧠 欢迎使用 IP-Sentinel Master (控制中枢) v${TARGET_VERSION}"
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
|
|
||||||
# [新增] 交互式操作菜单:支持选择部署或调用卸载程序
|
# [新增] 交互式操作菜单:支持选择部署或调用卸载程序
|
||||||
@@ -19,6 +31,9 @@ echo " 1) 🚀 部署 Master 控制中枢"
|
|||||||
echo " 2) 🗑️ 一键卸载 Master 中枢"
|
echo " 2) 🗑️ 一键卸载 Master 中枢"
|
||||||
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
||||||
|
|
||||||
|
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定被误删档
|
||||||
|
ACTION_CHOICE=${ACTION_CHOICE:-1}
|
||||||
|
|
||||||
if [ "$ACTION_CHOICE" == "2" ]; then
|
if [ "$ACTION_CHOICE" == "2" ]; then
|
||||||
echo -e "\n⏳ 正在拉取卸载程序..."
|
echo -e "\n⏳ 正在拉取卸载程序..."
|
||||||
# [新增逻辑] 使用上面定义的 REPO_RAW_URL 动态拉取卸载脚本,执行后自动销毁临时文件
|
# [新增逻辑] 使用上面定义的 REPO_RAW_URL 动态拉取卸载脚本,执行后自动销毁临时文件
|
||||||
@@ -29,13 +44,59 @@ if [ "$ACTION_CHOICE" == "2" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ================== [v3.1.1 延续: 安装前环境纯净度清理] ==================
|
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
||||||
echo -e "\n⏳ 正在清理旧版 Master 守护进程 (绝对安全保留 SQLite 数据库)..."
|
UPGRADE_MODE="false"
|
||||||
|
KEEP_DB="true"
|
||||||
|
|
||||||
|
if [ "$ACTION_CHOICE" == "1" ] && [ -f "${MASTER_DIR}/master.conf" ]; then
|
||||||
|
echo -e "\n\033[33m💡 司令部雷达提示:检测到本机已部署过 Master 中枢。\033[0m"
|
||||||
|
read -p "👉 是否按原配置直接进行平滑升级?(y/n, 默认y): " UPGRADE_CHOICE
|
||||||
|
if [[ -z "$UPGRADE_CHOICE" || "$UPGRADE_CHOICE" =~ ^[Yy]$ ]]; then
|
||||||
|
UPGRADE_MODE="true"
|
||||||
|
read -p "👉 是否保留历史节点数据库 (SQLite)?(y/n, 默认y): " DB_CHOICE
|
||||||
|
if [[ "$DB_CHOICE" =~ ^[Nn]$ ]]; then
|
||||||
|
KEEP_DB="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 汲取原配置进入内存
|
||||||
|
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"
|
||||||
|
else
|
||||||
|
echo -e "\033[33m🔄 您选择了重新配置,旧的中枢数据将被彻底抹除。\033[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# ================== [v3.2.2 优化: 安装前环境纯净度清理与数据保护] ==================
|
||||||
|
echo -e "\n⏳ 正在清理旧版 Master 守护进程..."
|
||||||
pkill -9 -f "tg_master.sh" >/dev/null 2>&1 || true
|
pkill -9 -f "tg_master.sh" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
|
if [ "$KEEP_DB" == "false" ]; then
|
||||||
|
rm -f "$DB_FILE" 2>/dev/null
|
||||||
|
echo -e "🗑️ 历史节点数据库已按指令清空。"
|
||||||
|
else
|
||||||
|
echo -e "📦 历史节点数据库 (SQLite) 已绝密保留。"
|
||||||
|
fi
|
||||||
|
# 删除旧的核心脚本,准备拉取新的
|
||||||
|
rm -f "${MASTER_DIR}/tg_master.sh" 2>/dev/null
|
||||||
|
else
|
||||||
|
# 焦土政策:如果不是升级模式,直接扬了整个司令部目录
|
||||||
|
rm -rf "$MASTER_DIR" 2>/dev/null
|
||||||
|
fi
|
||||||
|
echo -e "\033[32m✅ 旧进程已肃清!\033[0m"
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
|
|
||||||
# 1. 环境依赖安装
|
# 1. 环境依赖安装
|
||||||
echo "[1/4] 安装核心依赖 (curl, jq, sqlite3)..."
|
echo -e "\n[1/4] 安装核心依赖 (curl, jq, sqlite3)..."
|
||||||
if [ -f /etc/debian_version ]; then
|
if [ -f /etc/debian_version ]; then
|
||||||
apt-get update -y >/dev/null 2>&1
|
apt-get update -y >/dev/null 2>&1
|
||||||
apt-get install -y curl jq sqlite3 procps >/dev/null 2>&1
|
apt-get install -y curl jq sqlite3 procps >/dev/null 2>&1
|
||||||
@@ -45,17 +106,25 @@ fi
|
|||||||
|
|
||||||
mkdir -p "$MASTER_DIR"
|
mkdir -p "$MASTER_DIR"
|
||||||
|
|
||||||
# 2. 交互配置机器人
|
# ==========================================================
|
||||||
echo -e "\n[2/4] 配置控制中枢机器人:"
|
# 🛑 如果是全新部署,才询问 Token 并写入配置
|
||||||
read -p "请输入 Telegram Bot Token: " TG_TOKEN
|
# ==========================================================
|
||||||
|
if [ "$UPGRADE_MODE" == "false" ]; then
|
||||||
|
# 2. 交互配置机器人
|
||||||
|
echo -e "\n[2/4] 配置控制中枢机器人:"
|
||||||
|
read -p "请输入 Telegram Bot Token: " TG_TOKEN
|
||||||
|
|
||||||
cat > "${MASTER_DIR}/master.conf" << EOF
|
cat > "${MASTER_DIR}/master.conf" << EOF
|
||||||
|
# IP-Sentinel Master 本地固化配置 (v${TARGET_VERSION})
|
||||||
|
MASTER_VERSION="$TARGET_VERSION"
|
||||||
TG_TOKEN="$TG_TOKEN"
|
TG_TOKEN="$TG_TOKEN"
|
||||||
DB_FILE="$DB_FILE"
|
DB_FILE="$DB_FILE"
|
||||||
MASTER_DIR="$MASTER_DIR"
|
MASTER_DIR="$MASTER_DIR"
|
||||||
EOF
|
EOF
|
||||||
|
fi
|
||||||
|
# 🛑 拦截块结束
|
||||||
|
|
||||||
# 3. 初始化 SQLite 数据库
|
# 3. 初始化 SQLite 数据库 (幂等操作,升级模式下由 tg_master.sh 负责热修补)
|
||||||
echo -e "\n[3/4] 正在初始化 SQLite 数据库表结构..."
|
echo -e "\n[3/4] 正在初始化 SQLite 数据库表结构..."
|
||||||
sqlite3 "$DB_FILE" <<EOF
|
sqlite3 "$DB_FILE" <<EOF
|
||||||
CREATE TABLE IF NOT EXISTS nodes (
|
CREATE TABLE IF NOT EXISTS nodes (
|
||||||
@@ -64,6 +133,10 @@ CREATE TABLE IF NOT EXISTS nodes (
|
|||||||
agent_ip TEXT,
|
agent_ip TEXT,
|
||||||
agent_port TEXT,
|
agent_port TEXT,
|
||||||
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
|
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)
|
PRIMARY KEY(chat_id, node_name)
|
||||||
);
|
);
|
||||||
EOF
|
EOF
|
||||||
@@ -89,10 +162,17 @@ rm -f /tmp/cron_master
|
|||||||
# 立刻启动
|
# 立刻启动
|
||||||
pgrep -f tg_master.sh >/dev/null || nohup bash "${MASTER_DIR}/tg_master.sh" >/dev/null 2>&1 &
|
pgrep -f tg_master.sh >/dev/null || nohup bash "${MASTER_DIR}/tg_master.sh" >/dev/null 2>&1 &
|
||||||
|
|
||||||
|
# ================== [v3.2.2 优化: 战报文案分流] ==================
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
echo "🎉 Master 控制中枢部署完成!"
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
echo "🤖 机器人现已开始全局接客,等待边缘节点注册。"
|
echo "🎉 Master 控制中枢平滑热更新完成!"
|
||||||
|
echo "🤖 新版中枢引擎已接管数据库,继续等待边缘节点汇报。"
|
||||||
|
else
|
||||||
|
echo "🎉 Master 控制中枢部署完成!"
|
||||||
|
echo "🤖 机器人现已开始全局接客,等待边缘节点注册。"
|
||||||
|
fi
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
|
# =================================================================
|
||||||
|
|
||||||
# ================== [v3.1.2 新增: 玻璃房透明装机统计] ==================
|
# ================== [v3.1.2 新增: 玻璃房透明装机统计] ==================
|
||||||
echo -e "\n📡 正在向开源社区汇报装机量 (完全匿名,不收集IP)..."
|
echo -e "\n📡 正在向开源社区汇报装机量 (完全匿名,不收集IP)..."
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: tg_master.sh (Master 端调度枢纽 V3.0.4 动态签名版)
|
# 脚本名称: tg_master.sh (Master 端调度枢纽 - 动态锚点版)
|
||||||
# 核心功能: 监听 TG、操作 SQLite、Webhook 精准调度、403权限拦截、僵尸节点清理
|
# 核心功能: 监听 TG、操作 SQLite、Webhook 精准调度、403权限拦截、僵尸节点清理
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -9,6 +9,12 @@ CONF="/opt/ip_sentinel_master/master.conf"
|
|||||||
[ ! -f "$CONF" ] && exit 1
|
[ ! -f "$CONF" ] && exit 1
|
||||||
source "$CONF"
|
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"
|
OFFSET_FILE="${MASTER_DIR}/.tg_offset"
|
||||||
[[ -f $OFFSET_FILE ]] || echo "0" > $OFFSET_FILE
|
[[ -f $OFFSET_FILE ]] || echo "0" > $OFFSET_FILE
|
||||||
|
|
||||||
@@ -30,6 +36,13 @@ edit_msg() {
|
|||||||
-d "chat_id=$1" -d "message_id=$2" -d "text=$3" -d "parse_mode=Markdown" > /dev/null
|
-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() {
|
db_exec() {
|
||||||
sqlite3 "$DB_FILE" "$1"
|
sqlite3 "$DB_FILE" "$1"
|
||||||
@@ -54,9 +67,12 @@ generate_signed_url() {
|
|||||||
}
|
}
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
# ================== [v3.1.3 核心: 数据库结构无损热升级] ==================
|
# ================== [v3.1.3/v3.5.3 核心: 数据库结构无损热升级] ==================
|
||||||
# 自动探测并增加 region 字段,屏蔽已存在的报错,保护老节点数据
|
# 自动探测并增加缺失字段,屏蔽已存在的报错,保护老节点数据
|
||||||
db_exec "ALTER TABLE nodes ADD COLUMN region TEXT DEFAULT 'UNKNOWN';" 2>/dev/null
|
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
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
# --- 核心轮询循环 ---
|
# --- 核心轮询循环 ---
|
||||||
@@ -73,6 +89,23 @@ while true; do
|
|||||||
|
|
||||||
CHAT_ID=$(echo "$UPDATE" | jq -r '.message.chat.id // .callback_query.message.chat.id')
|
CHAT_ID=$(echo "$UPDATE" | jq -r '.message.chat.id // .callback_query.message.chat.id')
|
||||||
TEXT=$(echo "$UPDATE" | jq -r '.message.text // .callback_query.data')
|
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] ==================
|
# ================== [v3.0.1 新增: 消除转圈圈与获取消息ID] ==================
|
||||||
CB_ID=$(echo "$UPDATE" | jq -r '.callback_query.id // empty')
|
CB_ID=$(echo "$UPDATE" | jq -r '.callback_query.id // empty')
|
||||||
@@ -89,21 +122,27 @@ while true; do
|
|||||||
if [[ "$TEXT" == *"#REGISTER#"* ]]; then
|
if [[ "$TEXT" == *"#REGISTER#"* ]]; then
|
||||||
REG_LINE=$(echo "$TEXT" | grep "#REGISTER#" | head -n 1 | tr -d '\` ')
|
REG_LINE=$(echo "$TEXT" | grep "#REGISTER#" | head -n 1 | tr -d '\` ')
|
||||||
|
|
||||||
# V3.1.3 兼容性拆包: 判断是新版协议 (5个字段) 还是老版协议 (4个字段)
|
# V3.5.2 兼容性拆包: 支持 6字段(双轨)、5字段(单轨)、4字段(远古)
|
||||||
FIELD_COUNT=$(echo "$REG_LINE" | awk -F'|' '{print NF}')
|
FIELD_COUNT=$(echo "$REG_LINE" | awk -F'|' '{print NF}')
|
||||||
if [ "$FIELD_COUNT" -ge 5 ]; then
|
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
|
||||||
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
|
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
|
||||||
|
RAW_ALIAS="$RAW_NODE"
|
||||||
else
|
else
|
||||||
IFS='|' read -r MAGIC RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
|
IFS='|' read -r MAGIC RAW_NODE RAW_IP RAW_PORT <<< "$REG_LINE"
|
||||||
RAW_REGION="UNKNOWN"
|
RAW_REGION="UNKNOWN"
|
||||||
|
RAW_ALIAS="$RAW_NODE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 🛡️ 强制字符白名单过滤:保留历史特征不变
|
# 🛡️ 强制字符白名单过滤:保留历史特征不变
|
||||||
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
|
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)
|
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_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)
|
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
|
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 攻击渗透。"
|
send_msg "$CHAT_ID" "⛔ **安全拦截**:禁止注册内网或回环 IP,防止 SSRF 攻击渗透。"
|
||||||
@@ -115,9 +154,9 @@ while true; do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 入库时追加 region 字段
|
# [核心] 入库时追加 node_alias 字段
|
||||||
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';"
|
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" "✅ 司令部已确认!节点接入成功: \`$NODE_NAME\` ($AGENT_IP:$AGENT_PORT)"
|
send_msg "$CHAT_ID" "✅ **司令部确认 (v${MASTER_VERSION})**%0A节点 \`${NODE_ALIAS}\` 档案已录入!"
|
||||||
|
|
||||||
# ================== [v3.1.3 丝滑连招: 直接呼出全球大区雷达] ==================
|
# ================== [v3.1.3 丝滑连招: 直接呼出全球大区雷达] ==================
|
||||||
REGION_DATA=$(db_exec "SELECT region, COUNT(*) FROM nodes WHERE chat_id='$CHAT_ID' GROUP BY region;")
|
REGION_DATA=$(db_exec "SELECT region, COUNT(*) FROM nodes WHERE chat_id='$CHAT_ID' GROUP BY region;")
|
||||||
@@ -129,6 +168,7 @@ while true; do
|
|||||||
case "$REGION_NAME" in
|
case "$REGION_NAME" in
|
||||||
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
|
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
|
||||||
"SG") FLAG="🇸🇬" ;; "UK"|"GB") FLAG="🇬🇧" ;; "DE") FLAG="🇩🇪" ;; "FR") 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
|
esac
|
||||||
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
|
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
|
||||||
done <<< "$REGION_DATA"
|
done <<< "$REGION_DATA"
|
||||||
@@ -145,8 +185,16 @@ while true; do
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
case "$TEXT" in
|
case "$TEXT" in
|
||||||
"/start"|"/menu")
|
"/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\"}]]"
|
BTNS="[[{\"text\":\"🖥️ 我的节点列表\",\"callback_data\":\"list_nodes\"}], [{\"text\":\"🚀 全节点日报汇总\",\"callback_data\":\"all_reports\"}], [{\"text\":\"🛠️ 全节点一键维护\",\"callback_data\":\"all_run\"}]]"
|
||||||
send_ui "$CHAT_ID" "🛡️ **IP-Sentinel 司令部**\n欢迎回来,长官。请下达指令:" "$BTNS"
|
send_ui "$CHAT_ID" "🛡️ **IP-Sentinel 司令部**\n${VER_INFO}\n\n欢迎回来,长官。请下达指令:" "$BTNS"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
"all_reports")
|
"all_reports")
|
||||||
@@ -190,8 +238,9 @@ while true; do
|
|||||||
[ -z "$REGION_NAME" ] && REGION_NAME="UNKNOWN"
|
[ -z "$REGION_NAME" ] && REGION_NAME="UNKNOWN"
|
||||||
FLAG="🌐"
|
FLAG="🌐"
|
||||||
case "$REGION_NAME" in
|
case "$REGION_NAME" in
|
||||||
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
|
"US") FLAG="🇺🇸" ;; "JP") FLAG="🇯🇵" ;; "HK") FLAG="🇭🇰" ;;
|
||||||
"SG") FLAG="🇸🇬" ;; "UK"|"GB") FLAG="🇬🇧" ;; "DE") FLAG="🇩🇪" ;; "FR") 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
|
esac
|
||||||
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
|
BTNS="$BTNS[{\"text\":\"$FLAG $REGION_NAME ($NODE_COUNT 台)\",\"callback_data\":\"region:$REGION_NAME\"}],"
|
||||||
done <<< "$REGION_DATA"
|
done <<< "$REGION_DATA"
|
||||||
@@ -205,15 +254,17 @@ while true; do
|
|||||||
TARGET_REGION=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9')
|
TARGET_REGION=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9')
|
||||||
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
|
CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-')
|
||||||
|
|
||||||
NODE_LIST=$(db_exec "SELECT node_name FROM nodes WHERE chat_id='$CHAT_ID' AND region='$TARGET_REGION';")
|
# [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';")
|
||||||
if [ -z "$NODE_LIST" ]; then
|
if [ -z "$NODE_LIST" ]; then
|
||||||
send_msg "$CHAT_ID" "⚠️ 该战区下暂无可用节点。"
|
send_msg "$CHAT_ID" "⚠️ 该战区下暂无可用节点。"
|
||||||
else
|
else
|
||||||
BTNS="["
|
BTNS="["
|
||||||
COL=0
|
COL=0
|
||||||
ROW_STR="["
|
ROW_STR="["
|
||||||
for N in $NODE_LIST; do
|
while IFS='|' read -r N_NAME N_ALIAS; do
|
||||||
ROW_STR="$ROW_STR{\"text\":\"🖥️ $N\",\"callback_data\":\"manage:$N\"},"
|
[ -z "$N_NAME" ] && continue
|
||||||
|
ROW_STR="$ROW_STR{\"text\":\"🖥️ $N_ALIAS\",\"callback_data\":\"manage:$N_NAME\"},"
|
||||||
COL=$((COL+1))
|
COL=$((COL+1))
|
||||||
if [ $COL -eq 2 ]; then
|
if [ $COL -eq 2 ]; then
|
||||||
ROW_STR="${ROW_STR%,}]"
|
ROW_STR="${ROW_STR%,}]"
|
||||||
@@ -221,7 +272,7 @@ while true; do
|
|||||||
COL=0
|
COL=0
|
||||||
ROW_STR="["
|
ROW_STR="["
|
||||||
fi
|
fi
|
||||||
done
|
done <<< "$NODE_LIST"
|
||||||
# 如果是奇数,补齐最后的尾巴
|
# 如果是奇数,补齐最后的尾巴
|
||||||
if [ $COL -eq 1 ]; then
|
if [ $COL -eq 1 ]; then
|
||||||
ROW_STR="${ROW_STR%,}]"
|
ROW_STR="${ROW_STR%,}]"
|
||||||
@@ -234,11 +285,76 @@ while true; do
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
manage:*)
|
manage:*)
|
||||||
# 🛡️ 强制过滤节点名,防止面板渲染时发生 XSS 或注入
|
|
||||||
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
|
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
|
||||||
# 【核心升级】拆分下发按钮,精准对应 Google 与 Trust 两个模块,并排版为 3 行 2 列
|
TARGET_ALIAS=$(db_exec "SELECT IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
|
||||||
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\"}]]"
|
[ -z "$TARGET_ALIAS" ] && TARGET_ALIAS="$TARGET_NODE"
|
||||||
send_ui "$CHAT_ID" "⚙️ **目标锁定**: \`$TARGET_NODE\`\n请选择战术动作:" "$BTNS"
|
|
||||||
|
# [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
|
||||||
;;
|
;;
|
||||||
|
|
||||||
del:*)
|
del:*)
|
||||||
@@ -269,6 +385,50 @@ while true; do
|
|||||||
fi
|
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 前缀
|
||||||
google:*|trust:*|run:*|report:*|log:*)
|
google:*|trust:*|run:*|report:*|log:*)
|
||||||
# 🛡️ 提取并强制过滤动作参数、节点名与 CHAT_ID
|
# 🛡️ 提取并强制过滤动作参数、节点名与 CHAT_ID
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本)
|
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本 - 动态锚点版)
|
||||||
# 核心功能: 终止调度进程、清理看门狗定时任务、抹除数据库与配置
|
# 核心功能: 终止调度进程、清理看门狗定时任务、抹除数据库与配置
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
MASTER_DIR="/opt/ip_sentinel_master"
|
MASTER_DIR="/opt/ip_sentinel_master"
|
||||||
|
CONF_FILE="${MASTER_DIR}/master.conf"
|
||||||
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
echo " 🗑️ 准备卸载 IP-Sentinel Master (控制中枢)"
|
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 "========================================================"
|
||||||
|
|
||||||
echo -e "\n⚠️ 警告: 此操作将永久删除包含所有节点档案的 SQLite 数据库!"
|
echo -e "\n⚠️ 警告: 此操作将永久删除包含所有节点档案的 SQLite 数据库!"
|
||||||
@@ -20,7 +27,8 @@ fi
|
|||||||
|
|
||||||
# 1. 停止运行中的 Master 守护进程
|
# 1. 停止运行中的 Master 守护进程
|
||||||
echo "[1/3] 正在终止后台中枢调度进程..."
|
echo "[1/3] 正在终止后台中枢调度进程..."
|
||||||
pgrep -f tg_master.sh | xargs -r kill -9 >/dev/null 2>&1
|
# [优化] 使用 pkill 替代 pgrep | xargs,指令更短、容错率更高
|
||||||
|
pkill -9 -f "tg_master.sh" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
# 2. 清除看门狗定时任务 (Cron)
|
# 2. 清除看门狗定时任务 (Cron)
|
||||||
echo "[2/3] 正在清理系统定时任务 (Cron)..."
|
echo "[2/3] 正在清理系统定时任务 (Cron)..."
|
||||||
|
|||||||
83
scripts/fetch_trends.py
Normal file
83
scripts/fetch_trends.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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)
|
||||||
2
version.txt
Normal file
2
version.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
AGENT_VERSION=3.5.3
|
||||||
|
MASTER_VERSION=3.5.3
|
||||||
Reference in New Issue
Block a user