mirror of
https://github.com/hotyue/IP-Sentinel.git
synced 2026-05-11 23:09:46 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2d38a6e5 | ||
|
|
ca0c13c11a | ||
|
|
f29f21f274 | ||
|
|
d5f1850dbf | ||
|
|
50da8352d4 | ||
|
|
a55d362be6 | ||
|
|
044c7a9937 |
44
.github/workflows/daily_keywords.yml
vendored
44
.github/workflows/daily_keywords.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: Daily Trends Factory
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
# 每天 UTC 18:00 运行 (北京时间凌晨 02:00)
|
|
||||||
- cron: '0 18 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-trends:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Execute Trends Engine
|
|
||||||
run: python scripts/fetch_trends.py
|
|
||||||
|
|
||||||
- name: Commit and Push
|
|
||||||
run: |
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
git add data/keywords/
|
|
||||||
|
|
||||||
# 防御机制:如果没有新数据,就静默退出,不产生空提交
|
|
||||||
if git diff --staged --quiet; then
|
|
||||||
echo "No changes, skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 策略:放弃危险的 amend 强制覆盖,采用带日期的标准安全提交
|
|
||||||
git commit -m "chore(data): 🤖 自动机兵:刷新全战区热点词库 [$(date +'%Y-%m-%d')]"
|
|
||||||
git push origin main
|
|
||||||
127
README.md
127
README.md
@@ -10,22 +10,33 @@
|
|||||||
|
|
||||||
专为解决 VPS IP 被 Google 等数据库错误定位到中国大陆/香港(俗称“送中”)等问题而生。IP-Sentinel 已从单机脚本全面跃升为 **Master-Agent 分布式架构**。它像影子一样潜伏在全球各地的服务器后台,通过高度拟真的真实用户行为为你默默积累 IP 权重,并允许你通过 Telegram 随时随地对整个舰队进行毫秒级“点名”与“遥控”。
|
专为解决 VPS IP 被 Google 等数据库错误定位到中国大陆/香港(俗称“送中”)等问题而生。IP-Sentinel 已从单机脚本全面跃升为 **Master-Agent 分布式架构**。它像影子一样潜伏在全球各地的服务器后台,通过高度拟真的真实用户行为为你默默积累 IP 权重,并允许你通过 Telegram 随时随地对整个舰队进行毫秒级“点名”与“遥控”。
|
||||||
|
|
||||||
## ✨ 核心极客特性 (Core Architecture)
|
## ✨ 核心极客特性
|
||||||
|
|
||||||
- ⚡ **无损高并发引擎 (WAL Concurrency)**:司令部 SQLite 数据库全面激活 `WAL` (Write-Ahead Logging) 模式与毫秒级排队削峰算法。即使您同时对 500 台边缘节点发起全军总攻,也能完美规避 `database is locked` 与 Telegram `429` 频率拦截,实现 100% 战报送达。
|
- 🗺️ **全球拓扑矩阵 (Global Nexus)**:v3.1 跨洲际跃升。守护版图现已横跨亚、欧、美三大洲(美、日、英、德、法、新、港)。为每个国家注入极其硬核的“原生本地化”搜索词库与本土高权重站点(如政府、权威媒体、高铁网),真正实现“拟真融入”。
|
||||||
- 🪶 **抽脂级极简部署 (Zero-Bloat Native)**:全栈剔除 `pip`、`flask` 等臃肿第三方依赖,完全基于 Python3 原生标准库运行。安装底层强制注入 `--no-install-recommends` 防捆绑参数。无论是 128MB 内存的极简 NAT 小鸡,还是 Alpine/Arch Linux 特种系统,均可如丝般顺滑运行。
|
|
||||||
- 🎛️ **扁平化指挥矩阵 (Flat Command Matrix)**:[v3.6.1 重构] 引入扁平化 L0-L3 四级战区降维视图与双轨身份制。深度定制 Inline Keyboard 逃生舱交互,支持在统一哨兵终端进行原位丝滑重绘 (In-place UI Edit),实现毫秒级模块热启停与日志抓取,彻底告别刷屏烦恼。
|
|
||||||
- 🔄 **全栈零信任 OTA 引擎 (Zero-Trust OTA Upgrade)**:首创双端物理熔断机制。长官可通过私有中枢,一键向全舰队下发静默热重载指令;更支持**「司令部金蝉脱壳」**,中枢大脑可在此面板自我抛出幽灵进程进行免交互直装覆盖,实现真正的全栈去 SSH 化运维。
|
|
||||||
- 🛡️ **SSOT 溯源与热更新装甲 (Smooth Upgrade Engine)**:全系脚本彻底消灭硬编码,部署时动态抓取云端版本信标。自带状态机嗅探逻辑与防撞甲探测,即使是手动在终端运行安装,也仅需回车瞬间完成配置继承、数据同步与无损换代。
|
|
||||||
- 🗺️ **全球拓扑矩阵与活体词库 (Global Nexus)**:守护版图横跨亚欧美三大洲。接入 GitHub Actions 云端流水线,每日静默同步全球各大区当日 Google 真实热搜榜单与高权重本土站点,让伪装行为永远贴合当地网络脉搏。
|
|
||||||
- 👻 **资产持久化与错峰调度 (Hash-Seeded Persona)**:摒弃随机抽取指纹,基于节点物理 IP 哈希永久锁定 3 个绝对专属设备,完美构建高权重真实家庭内网画像。叠加按需智能分频与随机防并发休眠,化解“惊群效应”。
|
|
||||||
- 🖧 **底层路由死锁与高精度探针 (Hard-Bind Routing)**:底层探测引擎强力接管 curl 核心参数 (`--interface`),将发出的每一滴伪装流量死死绑定在物理网卡或隧道 IP 上。配合多级 ISP 容灾链路,彻底杜绝双栈环境下的流量溢出与 API 误判。
|
|
||||||
|
|
||||||
**—— 💎 骨干基建特征 ——**
|
- 👻 **设备资产持久化 (Hash-Seeded Persona)**:v3.2 核心换代。彻底摒弃传统的“随机抽取指纹”,引入基于节点物理 IP 的哈希锚定引擎。利用不可变哈希种子,为您的每台 VPS 在千万级指纹库中永久锁定 3 个绝对专属设备(如固定表现为 1台 Mac、1台 iPhone、1台 PC 交替上网)。完美构建高权重真实家庭内网画像,根除“僵尸网络”同质化特征!
|
||||||
- 🏭 **自动化指纹兵工厂**:依托 GitHub Actions CI/CD 流水线,每月 1 日无人值守锻造 4000+ 带绝对物理分区的真实终端设备数据。
|
|
||||||
- 🔒 **叹息之墙 (Zero-Trust HMAC)**:底层通讯引入 时间戳 + HMAC-SHA256 军用级动态签名。指令有效期仅 60 秒(阅后即焚),未授权请求直接触发系统级 403 物理熔断,彻底免疫中间人抓包与重放攻击。
|
- 🏭 **自动化指纹兵工厂 (Automated UA Factory)**:依托 GitHub Actions CI/CD 流水线,每月 1 日无人值守全自动生成 4000+ 带绝对物理分区的真实终端设备数据。配合边缘节点的守护进程静默拉取,实现千万级指纹资产的“自动驾驶”级演进。
|
||||||
- ☁️ **云端中枢 (Public Master)**:官方公共机器人 [@OmniBeacon_bot](https://t.me/OmniBeacon_bot) ,新手免自建,一键接入极速入伍!同时支持硬核极客私有化 SQLite 分布式部署。
|
|
||||||
- 👁️🗨️ **玻璃房透明遥测 (Glasshouse)**:基于 Cloudflare Workers 的全透明计数中枢,绝对零隐私收集,仅作原子累加,底层网关源码全开源。
|
- 🖧 **底层路由死锁 (Hard-Bind Routing)**:v3.2.1 热修复升级。底层探测引擎强力接管 curl 核心参数 (--interface),强制将发出的每一滴伪装流量死死绑定在您设定的物理网卡或隧道 IP 上,彻底杜绝双栈或多网卡环境下的流量溢出漏洞。
|
||||||
|
|
||||||
|
- 🎯 **多级容灾与高精度探针 (High-Precision Probe)**:v3.2.2 底层重构。重写战报模块与底层协议自适应逻辑,植入多级 ISP 容灾探针链路,并按“底层数据共识原则”智能清洗冗余 AS 号。确保在纯 V6、隧道或弱网环境下,数据获取依然 100% 精准畅通。
|
||||||
|
|
||||||
|
- 🔄 **平滑热更新装甲 (Smooth Upgrade Engine)**:v3.2.2 体验进化。全系植入状态机嗅探逻辑。无论是 Master 司令部还是 Agent 边缘节点,再次执行部署脚本时将自动识别并继承历史配置、SQLite 数据库与锚定 IP,一键回车即可瞬间完成无损换代,告别繁琐的重复配置。
|
||||||
|
|
||||||
|
- ☁️ **云端中枢 (Public Master)**:引入官方公共机器人 @OmniBeacon_bot,新手无需部署 Master 司令部,部署 Agent 时一键回车即可调用官方加密网关,30 秒极速入伍!
|
||||||
|
|
||||||
|
- 🧠 **分布式中枢 (Master-Agent)**:对于硬核极客,支持私有化部署。一台 Master 主控集成 SQLite 数据库,统管无数台 Agent 边缘节点,确保数据绝对私有。
|
||||||
|
|
||||||
|
- 🔒 **叹息之墙 (Zero-Trust HMAC)**:全面废弃明文 Token,底层通讯引入 时间戳 + HMAC-SHA256 军用级动态签名。指令有效期仅 60 秒(阅后即焚),彻底免疫中间人抓包、重放攻击与端口爆破。
|
||||||
|
|
||||||
|
- 🛡️ **工业级并发与自净引擎**:底层 Webhook 采用多线程模型彻底免疫慢速耗尽攻击;独创“智能清道夫”逻辑,覆盖安装/升级时自动绞杀僵尸进程与冗余定时任务,绝对纯净,告别玄学冲突。
|
||||||
|
|
||||||
|
- 🎮 **TG 战术面板 (Command Center)**:无需记忆繁琐命令,全 Inline Keyboard 交互。支持一键下发伪装指令、一键索要精准战报、毫秒级抓取边缘节点实时运行日志。
|
||||||
|
|
||||||
|
- 👁️🗨️ **玻璃房透明遥测 (Glasshouse Telemetry)**:引入基于 Cloudflare Workers 的全透明计数中枢,首页动态徽章实时展示全球真实装机与调用量。绝对零隐私收集,仅作原子累加,底层网关源码全开源,接受全网极客审计。
|
||||||
|
|
||||||
|
- ⚡ **丝滑战术交互 (Seamless UI)**:司令部交互面板像素级打磨。新节点发送暗号入伍成功后,司令部将无缝零延迟自动呼出最新的活跃节点阵列面板,彻底免除重复输入命令的繁琐,掌控感拉满。
|
||||||
|
|
||||||
## 📂 项目架构 (Monorepo)
|
## 📂 项目架构 (Monorepo)
|
||||||
|
|
||||||
@@ -38,64 +49,52 @@
|
|||||||
┣ 📂 core/ # 🛡️ 边缘哨兵:Webhook 被动监听、哈希锚定执行引擎
|
┣ 📂 core/ # 🛡️ 边缘哨兵:Webhook 被动监听、哈希锚定执行引擎
|
||||||
┣ 📂 scripts/ # 🐍 兵工厂引擎:基于 Python 的多物理分区 UA 生成器
|
┣ 📂 scripts/ # 🐍 兵工厂引擎:基于 Python 的多物理分区 UA 生成器
|
||||||
┣ 📂 data/ # 🗂️ 全球数据规则库 (动态拓扑)
|
┣ 📂 data/ # 🗂️ 全球数据规则库 (动态拓扑)
|
||||||
┃ ┣ 📜 map.json # 🌍 全球区域大脑 (v3.5.0 大洲战区拓扑)
|
┃ ┣ 📜 map.json # 🌐 全球区域索引大脑 (Master Index)
|
||||||
┃ ┣ 📂 regions/ # 🧊 冷数据:按 [国家/省州/城市] 深度细分的 LBS 锚点
|
┃ ┣ 📂 regions/ # 🧊 冷数据:按 [国家/省州/城市] 深度细分的 LBS 锚点
|
||||||
┃ ┣ 📂 keywords/ # 🔥 热数据:按国家归类的动态搜索词库 (OTA 自动更新)
|
┃ ┣ 📂 keywords/ # 🔥 热数据:按国家归类的动态搜索词库 (OTA 自动更新)
|
||||||
┃ ┗ 📜 user_agents.txt # 🔥 热数据:由兵工厂每月锻造的绝对坐标专属设备库
|
┃ ┗ 📜 user_agents.txt # 🔥 热数据:由兵工厂每月锻造的绝对坐标专属设备库
|
||||||
┣ 📜 version.txt # 🚩 双端版本信标:Agent/Master 独立解耦的 KV 环境配置
|
|
||||||
┗ 📂 telemetry/ # 👁️🗨️ 玻璃房计划:Cloudflare Workers 透明计数器网关源码
|
┗ 📂 telemetry/ # 👁️🗨️ 玻璃房计划:Cloudflare Workers 透明计数器网关源码
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 极速部署 (Quick Start)
|
## 🚀 极速部署 (Quick Start)
|
||||||
> 🛡️ **跨平台装甲支持**:Debian / Ubuntu / CentOS / RHEL / Alpine Linux / Arch Linux
|
|
||||||
系统现提供两种接入模式,请根据您的战术需求选择:
|
|
||||||
|
|
||||||
### 🔹 模式 A:私有独立模式 (全自主、强烈推荐)
|
v3.2.x 提供了两种接入模式,请根据您的战术需求选择:
|
||||||
适合追求绝对数据隐私与舰队最高控制权的领主。
|
|
||||||
|
|
||||||
> ☢️ **核按钮系统已就绪**:采用私有部署,您将解锁 **OTA 远程静默升级** 权限!所有私有前线节点均可通过您的 TG 面板实现一键全网代码热重载换代!
|
### 🔹 模式 A:官方公共模式 (最简、推荐)
|
||||||
|
**适合不想折腾、只想快速养护 IP 的新兵。**
|
||||||
- **部署 Master (中枢大脑)**:找一台 VPS 作为司令部(仅需部署一台),执行:
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/master/install_master.sh -o /tmp/ins_master.sh && sudo bash /tmp/ins_master.sh
|
|
||||||
```
|
|
||||||
- 部署 Agent (边缘哨兵):在需要养护的机器上执行 Agent 脚本,安装时选择私有独立中枢,并分别输入您自建机器人的 [Token](https://blog.iot-architect.com/engineering-practice/create-private-telegram-bot-via-botfather) 以及您的个人 [Chat ID](https://blog.iot-architect.com/engineering-practice/get-telegram-personal-id-via-userinfobot) :
|
|
||||||
|
|
||||||
|
1. **关注机器人**:在 TG 中关注 [@OmniBeacon_bot](https://t.me/OmniBeacon_bot) 并发送 `/start`。
|
||||||
|
2. **部署 Agent**:在目标 VPS 上执行以下指令,安装过程中**直接回车**使用官方机器人,并输入您的 Chat ID:
|
||||||
```Bash
|
```Bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/install.sh -o /tmp/ins_agent.sh && sudo bash /tmp/ins_agent.sh
|
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/install.sh)
|
||||||
|
|
||||||
```
|
```
|
||||||
- 激活节点:安装完成后,您的手机会收到一条 #REGISTER# 注册暗号,将其转发给您自己的机器人即可完成编队入库。
|
3. **激活节点**:安装完成后,您的手机会收到一条 #REGISTER# 暗号,将其转发给机器人即可完成入库。
|
||||||
|
|
||||||
### 🔸 模式 B:官方公共模式 (最简体验)
|
### 🔸 模式 B:私有独立模式 (全自主、硬核)
|
||||||
适合不想折腾、只想快速体验养护效果的新兵。
|
**适合追求绝对数据隐私、需自建机器人的领主。**
|
||||||
|
|
||||||
- 关注机器人:在 TG 中关注官方安全网关 [@OmniBeacon_bot](https://t.me/OmniBeacon_bot) 并发送 /start。
|
|
||||||
|
|
||||||
- 部署 Agent:在目标 VPS 上执行以下指令,安装过程中选择官方公共网关,并输入您的 Chat ID:
|
|
||||||
|
|
||||||
|
1. **部署 Master**:找一台 VPS 作为大脑(仅需部署一台),执行:
|
||||||
```Bash
|
```Bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/install.sh -o /tmp/ins_agent.sh && sudo bash /tmp/ins_agent.sh
|
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/master/install_master.sh)
|
||||||
|
|
||||||
```
|
```
|
||||||
- 激活节点:同上,将收到的暗号转发给官方机器人即可。
|
2. **部署 Agent**:在需要养护的机器上执行 Agent 脚本,输入您自建机器人的 Token 以及与 Master 一致的配置。
|
||||||
|
```Bash
|
||||||
|
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/main/core/install.sh)
|
||||||
|
|
||||||
## 🆙 架构级无损热升级指引 (Upgrade Guide)
|
```
|
||||||
|
3. **激活节点**:同上,将暗号转发给您自己的机器人即可。
|
||||||
|
|
||||||
### 📡 方式一:OTA 远程静默升级 (私有中枢专属)
|
### ⚠️ 平滑升级指引 (Upgrade to v3.2.2)
|
||||||
如果您是私有中枢领主,当司令部首页 (`/start`) 或每日战报提示发现新版本时:
|
|
||||||
|
|
||||||
1. **升级 Master 司令部自身**:在司令部顶级菜单,点击最上方的 `[ 🆙 升级司令部至 vX.X.X ]`。中枢将释放幽灵进程静默重构,数秒后向您发送捷报。
|
得益于 **v3.2.2 全新引入的平滑热更新引擎 (Smooth Upgrade Engine)**,系统升级现已变得极其优雅与安全。
|
||||||
2. **升级全舰队 Agent**:在司令部顶级菜单,点击 `[ ☢️ 全舰队 OTA 热重载 ]`。
|
|
||||||
3. **升级单节点 Agent**:进入 `🌍 全球战区雷达` -> 选择目标节点 -> 在统一终端面板点击 `[ 🆙 OTA 静默升级 ]`。
|
|
||||||
*(⚠️ 节点收到指令后会在后台挂起静默拉取,全程无需登录 SSH,完成后将主动发回心跳确认!)*
|
|
||||||
|
|
||||||
### 💻 方式二:SSH 终端平滑直装 (适用于官方网关或老旧节点)
|
无需卸载旧版本,无论您是要升级 Agent 边缘节点还是 Master 控制中枢,只需在您的终端中**再次运行上方对应的官方部署指令**。
|
||||||
如果您的节点不支持 OTA,或者您的节点版本过于陈旧 (如 v3.3.1):
|
|
||||||
|
|
||||||
- 登录该节点的 SSH 终端,再次运行上面的 core/install.sh 官方安装指令。
|
安装雷达会自动嗅探您的历史部署状态(包括您的 Token、区域设定、SQLite 数据库及物理网卡锚点)。当询问是否平滑升级时,您只需**一路回车 (默认选 y)**,脚本将在短短 3 秒内瞬间完成核心装甲的无损换脑手术,您的所有战术资产将得到 100% 保留!
|
||||||
|
|
||||||
- 安装引擎自带状态机嗅探逻辑,它会自动读取老旧数据,您只需一路回车,3 秒即可在本地完成配置继承、数据同步与新内核的无损覆盖热重载!
|
🗑️ 一键无痕卸载
|
||||||
|
|
||||||
## 🗑️ 一键无痕卸载
|
|
||||||
如果你需要清理某个边缘节点,只需重新运行 `core/install.sh` 并选择 **[2]**,或直接在节点终端执行:
|
如果你需要清理某个边缘节点,只需重新运行 `core/install.sh` 并选择 **[2]**,或直接在节点终端执行:
|
||||||
|
|
||||||
```Bash
|
```Bash
|
||||||
@@ -103,7 +102,7 @@ bash /opt/ip_sentinel/core/uninstall.sh
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🧓 传家宝老旧系统专用通道 (Debian 9)
|
### 🧓 传家宝老旧系统专用通道 (Debian 9)
|
||||||
|
|
||||||
如果你的小鸡系统版本过低(如 Debian 9),由于官方 APT 源已关闭且 Python 版本过旧,无法使用主线版本,请使用 **Legacy 兼容分支** 部署。
|
如果你的小鸡系统版本过低(如 Debian 9),由于官方 APT 源已关闭且 Python 版本过旧,无法使用主线版本,请使用 **Legacy 兼容分支** 部署。
|
||||||
*(注意:该分支仅作基础维护,不享受新功能迭代,请尽可能升级你的系统)*
|
*(注意:该分支仅作基础维护,不享受新功能迭代,请尽可能升级你的系统)*
|
||||||
@@ -112,36 +111,20 @@ bash /opt/ip_sentinel/core/uninstall.sh
|
|||||||
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy/core/install.sh)
|
bash <(curl -sL https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy/core/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📡 战术联络 (Community)
|
📡 战术联络 (Community)
|
||||||
|
|
||||||
如果你在使用过程中遇到任何疑难杂症,或者想围观大佬们的养护战报,欢迎加入我们的基地:
|
如果你在使用过程中遇到任何疑难杂症,或者想围观大佬们的养护战报,欢迎加入我们的基地:
|
||||||
- Telegram 频道: [@IP_Sentinel_Matrix](https://t.me/IP_Sentinel_Matrix)
|
- Telegram 频道: [@IP_Sentinel_Matrix](https://t.me/IP_Sentinel_Matrix)
|
||||||
|
|
||||||
## 🤝 参与贡献 (Contributors)
|
🤝 参与贡献
|
||||||
|
如果你想为项目增加新的节点区域(例如德国、英国、新加坡等),或者提供更丰富的本土化搜索词库,非常欢迎提交 Pull Request!
|
||||||
|
|
||||||
**🌟 感谢以下所有为 IP-Sentinel 添砖加瓦的指挥官们!** 你们的每一次 PR 都在让这艘战舰的全球雷达覆盖得更广。
|
**v3.0 全球节点贡献规范:**
|
||||||
|
|
||||||
<a href="https://github.com/hotyue/IP-Sentinel/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=hotyue/IP-Sentinel" alt="Contributors" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
如果你想为项目增加新的节点区域(例如德国、英国、大洋洲等),或者提供更丰富的本土化搜索词库,非常欢迎提交 Pull Request!
|
|
||||||
|
|
||||||
**💡 全球节点贡献规范:**
|
|
||||||
1. 在 `data/regions/国家代码/省州代码/` 目录下新增对应城市的配置 `.json`。
|
1. 在 `data/regions/国家代码/省州代码/` 目录下新增对应城市的配置 `.json`。
|
||||||
2. 在 `data/keywords/` 目录下新增或完善配套国家的词库 `kw_XX.txt`。
|
2. 在 `data/keywords/` 目录下新增或完善配套国家的词库 `kw_XX.txt`。
|
||||||
3. **最重要的一步:** 在 `data/map.json` 中登记你的国家、省州与城市信息。安装脚本将自动读取地图,在全球雷达中点亮你的节点!
|
3. **最重要的一步:** 在 `data/map.json` 中登记你的国家、省州与城市信息。安装脚本将自动读取地图,在全球雷达中点亮你的节点!
|
||||||
|
|
||||||
## ⚠️ 免责声明
|
⚠️ 免责声明
|
||||||
|
|
||||||
本项目仅供网络原理研究、个人 VPS 维护学习使用。请遵守当地法律法规及目标服务商的 TOS(服务条款),切勿用于恶意高频请求或任何非法用途。使用者需自行承担因不当使用造成的 IP 封禁或其他相关风险。
|
本项目仅供网络原理研究、个人 VPS 维护学习使用。请遵守当地法律法规及目标服务商的 TOS(服务条款),切勿用于恶意高频请求或任何非法用途。使用者需自行承担因不当使用造成的 IP 封禁或其他相关风险。
|
||||||
|
|
||||||
## 保持联系
|
|
||||||
|
|
||||||
[](https://blog.iot-architect.com)
|
|
||||||
|
|
||||||
如果你觉得这个项目对你有帮助,欢迎关注我的个人博客,我会定期分享技术教程。
|
|
||||||
|
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
[](https://starchart.cc/hotyue/IP-Sentinel)
|
[](https://starchart.cc/hotyue/IP-Sentinel)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 - 动态锚点版)
|
# 脚本名称: agent_daemon.sh (受控节点 Webhook 守护进程 V3.0.3)
|
||||||
# 核心功能: 智能防打扰注册、进程自检、模块级路由分发(403拦截)
|
# 核心功能: 智能防打扰注册、进程自检、模块级路由分发(403拦截)
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -17,26 +17,21 @@ source "$CONFIG_FILE"
|
|||||||
|
|
||||||
# 默认 Webhook 监听端口
|
# 默认 Webhook 监听端口
|
||||||
AGENT_PORT=${AGENT_PORT:-9527}
|
AGENT_PORT=${AGENT_PORT:-9527}
|
||||||
# [v3.5.2 核心] 载入不可变主键与可变展示名 (双轨身份)
|
NODE_NAME=$(hostname | cut -c 1-15)
|
||||||
if [ -z "$NODE_NAME" ]; then
|
|
||||||
IP_HASH=$(echo "${PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
|
# --- [重点升级 1: 守护进程防冲突自检] ---
|
||||||
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${IP_HASH}"
|
if pgrep -f "webhook.py $AGENT_PORT" > /dev/null; then
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
NODE_ALIAS="${NODE_ALIAS:-$NODE_NAME}"
|
|
||||||
|
|
||||||
|
# 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:]')
|
||||||
|
|
||||||
# [v3.3.1 修改] 为新获取到的 v6 自动加方括号;如果网络波动没抓到,强制信任本地 config 中的公网面孔
|
# 为新获取到的 v6 自动加方括号,以确保与之前锁定的格式对齐比对
|
||||||
if [ -n "$RAW_IP" ]; then
|
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
|
||||||
if [[ "$RAW_IP" == *":"* ]] && [[ "$RAW_IP" != *"["* ]]; then
|
AGENT_IP="[${RAW_IP}]"
|
||||||
AGENT_IP="[${RAW_IP}]"
|
|
||||||
else
|
|
||||||
AGENT_IP="$RAW_IP"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
AGENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
|
AGENT_IP="$RAW_IP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$AGENT_IP" ]; then
|
if [ -n "$AGENT_IP" ]; then
|
||||||
@@ -46,8 +41,8 @@ if [ -n "$AGENT_IP" ]; then
|
|||||||
|
|
||||||
# 只有当这是第一次运行,或者公网 IP 发生变动时,才发送 Telegram 申请
|
# 只有当这是第一次运行,或者公网 IP 发生变动时,才发送 Telegram 申请
|
||||||
if [ "$AGENT_IP" != "$LAST_IP" ]; then
|
if [ "$AGENT_IP" != "$LAST_IP" ]; then
|
||||||
# [v3.5.2 核心] 携带 6 字段双轨身份发起注册申请 (展示别名,暗号尾部追加 NODE_ALIAS)
|
# V3.1.3 协议升级: 在底部暗号中精准嵌入 ${REGION_CODE} 大区标识
|
||||||
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}\`"
|
REG_MSG="👋 **[边缘节点接入申请]**%0A大区: \`${REGION_CODE}\`%0A节点: \`${NODE_NAME}\`%0A地址: \`${AGENT_IP}:${AGENT_PORT}\`%0A%0A⚠️ **安全验证**: 为防止非法节点接入,请长按复制下方代码,并**发送给我**以完成最终授权录入:%0A%0A\`#REGISTER#|${REGION_CODE}|${NODE_NAME}|${AGENT_IP}|${AGENT_PORT}\`"
|
||||||
|
|
||||||
curl -s -m 5 -X POST "${TG_API_URL}" \
|
curl -s -m 5 -X POST "${TG_API_URL}" \
|
||||||
-d "chat_id=${CHAT_ID}" \
|
-d "chat_id=${CHAT_ID}" \
|
||||||
@@ -61,20 +56,6 @@ if [ -n "$AGENT_IP" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ================== [v3.6.3 新增: 自动生成自签名 TLS 加密证书] ==================
|
|
||||||
# [修复] 仅在私有中枢模式下生成证书。官方网关模式下,CF Worker 严格拒绝自签名,必须回退 HTTP
|
|
||||||
if [ "$TG_TOKEN" != "OFFICIAL_GATEWAY_MODE" ]; then
|
|
||||||
CERT_FILE="${INSTALL_DIR}/core/cert.pem"
|
|
||||||
KEY_FILE="${INSTALL_DIR}/core/key.pem"
|
|
||||||
if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
|
|
||||||
echo "🔐 [Agent] 正在生成本地自签名 TLS 加密证书 (2048位 RSA)..."
|
|
||||||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
||||||
-keyout "$KEY_FILE" -out "$CERT_FILE" \
|
|
||||||
-subj "/C=US/O=IP-Sentinel/CN=Agent-Sec" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
# 3. 启动轻量级 Python3 Webhook 监听服务 (v3.0.4 动态 HMAC 签名防重放)
|
# 3. 启动轻量级 Python3 Webhook 监听服务 (v3.0.4 动态 HMAC 签名防重放)
|
||||||
cat > "${INSTALL_DIR}/core/webhook.py" << 'EOF'
|
cat > "${INSTALL_DIR}/core/webhook.py" << 'EOF'
|
||||||
import http.server
|
import http.server
|
||||||
@@ -134,7 +115,7 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 校验 3:HMAC 数据完整性与身份合法性校验
|
# 校验 3:HMAC 数据完整性与身份合法性校验
|
||||||
msg = f"{req_path}:{req_t}".encode('utf-8')
|
msg = "{}:{}".format(req_path, req_t).encode('utf-8')
|
||||||
expected_sign = hmac.new(AUTH_TOKEN.encode('utf-8'), msg, hashlib.sha256).hexdigest()
|
expected_sign = hmac.new(AUTH_TOKEN.encode('utf-8'), msg, hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
# 使用 compare_digest 防御时序攻击
|
# 使用 compare_digest 防御时序攻击
|
||||||
@@ -219,11 +200,8 @@ class AgentHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
if lines:
|
if lines:
|
||||||
log_data = html.escape("".join(lines[-15:]))
|
log_data = html.escape("".join(lines[-15:]))
|
||||||
|
|
||||||
# [v3.5.2 核心] 获取版本与节点展示别名
|
node_name = subprocess.check_output(['hostname']).decode('utf-8').strip()[:15]
|
||||||
local_ver = config.get('AGENT_VERSION', '未知')
|
text_msg = "📄 <b>[{}] 实时运行日志:</b>\n<pre><code>{}</code></pre>".format(node_name, log_data)
|
||||||
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', ''),
|
||||||
@@ -234,196 +212,13 @@ 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("Log transmission failed: {}".format(e))
|
||||||
|
|
||||||
# 路由 5: 节点重命名展示别名同步接口 (Base64 终极防御版)
|
|
||||||
elif req_path == '/trigger_rename':
|
|
||||||
b64_alias = query.get('b64', [''])[0]
|
|
||||||
if not b64_alias:
|
|
||||||
self.send_response(400)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"400 Bad Request: Alias is empty\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
import re
|
|
||||||
import base64
|
|
||||||
try:
|
|
||||||
# 1. 还原 URL 安全的 Base64 字符并解码 (杜绝乱码与 WAF 拦截)
|
|
||||||
pad = len(b64_alias) % 4
|
|
||||||
if pad > 0:
|
|
||||||
b64_alias += '=' * (4 - pad)
|
|
||||||
b64_alias = b64_alias.replace('-', '+').replace('_', '/')
|
|
||||||
raw_alias = base64.b64decode(b64_alias).decode('utf-8', errors='ignore')
|
|
||||||
|
|
||||||
# 2. 强清洗:杜绝 TG Markdown 崩溃,严格限制中英数,最大20字符
|
|
||||||
decoded_alias = raw_alias.replace('_', '-')
|
|
||||||
safe_alias = re.sub(r'[^a-zA-Z0-9\-\u4e00-\u9fa5]', '', decoded_alias)[:20]
|
|
||||||
|
|
||||||
if safe_alias:
|
|
||||||
# 3. 强容错读写 config.conf (引入 fcntl 排他锁与 r+ 模式防并发清空)
|
|
||||||
config_path = '/opt/ip_sentinel/config.conf'
|
|
||||||
import fcntl
|
|
||||||
with open(config_path, 'r+', encoding='utf-8', errors='ignore') as f:
|
|
||||||
fcntl.flock(f, fcntl.LOCK_EX)
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
alias_found = False
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if line.startswith('NODE_ALIAS='):
|
|
||||||
lines[i] = f'NODE_ALIAS="{safe_alias}"\n'
|
|
||||||
alias_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not alias_found:
|
|
||||||
lines.append(f'NODE_ALIAS="{safe_alias}"\n')
|
|
||||||
|
|
||||||
f.seek(0)
|
|
||||||
f.writelines(lines)
|
|
||||||
f.truncate()
|
|
||||||
fcntl.flock(f, fcntl.LOCK_UN)
|
|
||||||
|
|
||||||
# [v3.5.2 极致丝滑] 移除向 TG 推送冗余报文的逻辑,直接向 Master 回执成功状态即可
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/plain")
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"Action Accepted: trigger_rename\n")
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
self.send_response(500)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(f"500 Internal Error: {str(e)}\n".encode('utf-8'))
|
|
||||||
return
|
|
||||||
|
|
||||||
self.send_response(400)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"400 Bad Request: Invalid Characters\n")
|
|
||||||
|
|
||||||
# ================== [v3.5.3 新增: 模块动态启停接口] ==================
|
|
||||||
elif req_path == '/trigger_toggle':
|
|
||||||
mod_name = query.get('mod', [''])[0]
|
|
||||||
target_state = query.get('state', [''])[0].lower()
|
|
||||||
|
|
||||||
if mod_name not in ['google', 'trust'] or target_state not in ['true', 'false']:
|
|
||||||
self.send_response(400)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"400 Bad Request: Invalid parameters\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
config_key = f"ENABLE_{mod_name.upper()}="
|
|
||||||
|
|
||||||
try:
|
|
||||||
config_path = '/opt/ip_sentinel/config.conf'
|
|
||||||
import fcntl
|
|
||||||
|
|
||||||
with open(config_path, 'r+', encoding='utf-8', errors='ignore') as f:
|
|
||||||
fcntl.flock(f, fcntl.LOCK_EX)
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
found = False
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if line.startswith(config_key):
|
|
||||||
lines[i] = f'{config_key}"{target_state}"\n'
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
lines.append(f'{config_key}"{target_state}"\n')
|
|
||||||
|
|
||||||
f.seek(0)
|
|
||||||
f.writelines(lines)
|
|
||||||
f.truncate()
|
|
||||||
fcntl.flock(f, fcntl.LOCK_UN)
|
|
||||||
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/plain")
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"Action Accepted: trigger_toggle\n")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.send_response(500)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(f"500 Internal Error: {str(e)}\n".encode('utf-8'))
|
|
||||||
|
|
||||||
# ================== [v3.6.0 新增: 零信任 OTA 远程静默升级路由] ==================
|
|
||||||
elif req_path == '/trigger_ota':
|
|
||||||
try:
|
|
||||||
# 动态读取最新 config 内存态
|
|
||||||
config_mem = {}
|
|
||||||
config_path = '/opt/ip_sentinel/config.conf'
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, 'r', errors='ignore') as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if '=' in line and not line.startswith('#'):
|
|
||||||
key, val = line.split('=', 1)
|
|
||||||
config_mem[key] = val.strip('"\'')
|
|
||||||
|
|
||||||
# 🛡️ 熔断校验 1: Agent 本地是否开启了 OTA 授权
|
|
||||||
if config_mem.get('ENABLE_OTA', 'false').lower() != 'true':
|
|
||||||
self.send_response(403)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"403 Forbidden: OTA Upgrade Disabled locally\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 🛡️ 熔断校验 2: 是否处于官方公共网关下 (强行硬编码拦截)
|
|
||||||
if config_mem.get('TG_TOKEN', '') == 'OFFICIAL_GATEWAY_MODE':
|
|
||||||
self.send_response(403)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"403 Forbidden: OTA strictly disabled under Public Gateway mode\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 校验通过,立即返回 200 回执,释放 Master 连接池
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/plain")
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(b"Action Accepted: trigger_ota\n")
|
|
||||||
|
|
||||||
# [修复] 逃逸 Systemd Cgroup,并引入 bash -n 语法树校验防砖机制
|
|
||||||
import shutil
|
|
||||||
import base64
|
|
||||||
repo_url = "https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
|
||||||
|
|
||||||
# 动态构建报错回执文本 (第一层 Base64 隔离换行与特殊字符)
|
|
||||||
err_msg = f"❌ **OTA 熔断告警**\n📍 节点: `{config_mem.get('NODE_ALIAS', '未知')}`\n⚠️ 原因: 脚本语法校验(bash -n)未通过,下载可能不完整。\n🚀 状态: 升级已取消,节点安全。"
|
|
||||||
err_msg_b64 = base64.b64encode(err_msg.encode('utf-8')).decode('utf-8')
|
|
||||||
|
|
||||||
tg_url = config_mem.get('TG_API_URL', '')
|
|
||||||
chat_id = config_mem.get('CHAT_ID', '')
|
|
||||||
|
|
||||||
# [v3.6.3 究极防御] 采用 Base64 将整个 OTA 执行脚本封装 (第二层隔离)
|
|
||||||
# 彻底免疫因为 python 变量掺杂引号而导致的 shell 注入或截断
|
|
||||||
ota_script = f"""
|
|
||||||
export SILENT_OTA="true"
|
|
||||||
curl -fsSL {repo_url}/core/install.sh -o /tmp/ota_agent.sh
|
|
||||||
if bash -n /tmp/ota_agent.sh; then
|
|
||||||
bash /tmp/ota_agent.sh > /opt/ip_sentinel/logs/ota_upgrade.log 2>&1
|
|
||||||
else
|
|
||||||
MSG=$(echo '{err_msg_b64}' | base64 -d)
|
|
||||||
curl -s -m 10 -X POST "{tg_url}" -d "chat_id={chat_id}" -d "text=$MSG" -d "parse_mode=Markdown" > /dev/null 2>&1
|
|
||||||
echo "OTA Checksum Failed: Script corrupted" > /opt/ip_sentinel/logs/ota_upgrade.log
|
|
||||||
fi
|
|
||||||
"""
|
|
||||||
ota_script_b64 = base64.b64encode(ota_script.encode('utf-8')).decode('utf-8')
|
|
||||||
|
|
||||||
# 安全解包并执行
|
|
||||||
if shutil.which("systemd-run"):
|
|
||||||
full_cmd = f"systemd-run --quiet --no-block bash -c \"echo '{ota_script_b64}' | base64 -d | bash\""
|
|
||||||
else:
|
|
||||||
full_cmd = f"nohup bash -c \"echo '{ota_script_b64}' | base64 -d | bash\" >/dev/null 2>&1 &"
|
|
||||||
|
|
||||||
subprocess.Popen(full_cmd, shell=True)
|
|
||||||
|
|
||||||
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()
|
||||||
@@ -433,49 +228,21 @@ fi
|
|||||||
|
|
||||||
import socket
|
import socket
|
||||||
# ================== [v3.0.3 变更: 引入多线程模型抵抗 Slowloris 攻击] ==================
|
# ================== [v3.0.3 变更: 引入多线程模型抵抗 Slowloris 攻击] ==================
|
||||||
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
class ThreadedDualStackServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||||
allow_reuse_address = True # 开启端口复用,防止热重启时端口冲突
|
allow_reuse_address = True # 开启端口复用,防止热重启时端口冲突
|
||||||
|
address_family = socket.AF_INET6 if socket.has_ipv6 else socket.AF_INET
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 优先尝试监听双栈/IPv6 (大多数 Linux 默认支持 IPv4 映射接入)
|
bind_addr = "::" if socket.has_ipv6 else ""
|
||||||
ThreadedServer.address_family = socket.AF_INET6
|
httpd = ThreadedDualStackServer((bind_addr, PORT), AgentHandler)
|
||||||
httpd = ThreadedServer(("::", PORT), AgentHandler)
|
|
||||||
except Exception:
|
|
||||||
# 2. [核心修复 Issue #23] 若系统内核已禁用 IPv6,抛弃报错,智能回退至纯 IPv4 监听
|
|
||||||
ThreadedServer.address_family = socket.AF_INET
|
|
||||||
httpd = ThreadedServer(("0.0.0.0", PORT), AgentHandler)
|
|
||||||
|
|
||||||
# ================== [v3.6.3 核心: 挂载 TLS 加密隧道 (动态适配兼容版)] ==================
|
|
||||||
import ssl
|
|
||||||
cert_path = '/opt/ip_sentinel/core/cert.pem'
|
|
||||||
key_path = '/opt/ip_sentinel/core/key.pem'
|
|
||||||
|
|
||||||
# 核心判定:提取配置中的 TOKEN 标识
|
|
||||||
is_official_gateway = False
|
|
||||||
if os.path.exists('/opt/ip_sentinel/config.conf'):
|
|
||||||
with open('/opt/ip_sentinel/config.conf', 'r') as f:
|
|
||||||
for line in f:
|
|
||||||
if line.startswith('TG_TOKEN=') and 'OFFICIAL_GATEWAY_MODE' in line:
|
|
||||||
is_official_gateway = True
|
|
||||||
break
|
|
||||||
|
|
||||||
# 仅在非官方网关且证书存在时,才挂载 TLS 装甲
|
|
||||||
if not is_official_gateway and os.path.exists(cert_path) and os.path.exists(key_path):
|
|
||||||
try:
|
|
||||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
||||||
context.load_cert_chain(certfile=cert_path, keyfile=key_path)
|
|
||||||
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"SSL 隧道构建失败,退化为 HTTP: {e}")
|
|
||||||
# ======================================================================================
|
|
||||||
|
|
||||||
try:
|
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
# ====================================================================================
|
# ====================================================================================
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# --- [重点升级 3: 移交系统级守护进程接管 (阻塞模式)] ---
|
# --- [重点升级 3: 真正的静默后台启动] ---
|
||||||
echo "🚀 [Agent] 正在启动 Webhook 监听服务 (端口: $AGENT_PORT)..."
|
echo "🚀 [Agent] 正在后台启动 Webhook 监听服务 (端口: $AGENT_PORT)..."
|
||||||
exec python3 "${INSTALL_DIR}/core/webhook.py" "$AGENT_PORT"
|
nohup python3 "${INSTALL_DIR}/core/webhook.py" "$AGENT_PORT" > /dev/null 2>&1 &
|
||||||
|
disown 2>/dev/null || true
|
||||||
|
echo "✅ [Agent] 守护进程启动完毕,可安全关闭终端。"
|
||||||
665
core/install.sh
665
core/install.sh
@@ -1,110 +1,43 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 - 动态锚点版)
|
# 脚本名称: install.sh (IP-Sentinel 分布式边缘节点部署脚本 v3.3.0 - OTA 活体引擎)
|
||||||
# 核心功能: 战区分组菜单、模块按需开启、官方机器人一键配置、版本状态机路由
|
# 核心功能: 区域选择、模块按需开启、官方机器人一键配置、平滑热更新、分频错峰调度
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 🛑 核心权限防线: 检查是否以 root 权限运行
|
|
||||||
# ==========================================================
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
echo -e "\033[31m❌ 权限被拒绝: 部署 IP-Sentinel 需要最高系统权限。\033[0m"
|
|
||||||
echo -e "💡 请切换到 root 用户 (执行 su root 或 sudo -i) 后重新运行指令。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 你的 GitHub 仓库 Raw 数据直链前缀
|
# 你的 GitHub 仓库 Raw 数据直链前缀
|
||||||
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy"
|
||||||
# 临时改为开发地址用于测试
|
# 临时改为私库地址用于测试
|
||||||
# REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/v3.6.2-rc"
|
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/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 解析法)]
|
echo "========================================================"
|
||||||
TARGET_VERSION=$(curl -s -m 3 "${REPO_RAW_URL}/version.txt" | grep "^AGENT_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
|
echo " 🛡️ 欢迎使用 IP-Sentinel (边缘节点 Edge Agent)"
|
||||||
# 🛡️ 兜底防线:如果网络波动拉取失败,启用内置的安全兜底版本
|
echo "========================================================"
|
||||||
TARGET_VERSION=${TARGET_VERSION:-"3.5.1"}
|
|
||||||
|
|
||||||
# 轻量级版本号比对函数 (例如: version_lt "3.3.1" "3.4.0" 返回 true)
|
# 1. 依赖检查与安装 (新增 python3 用于轻量级 Webhook 服务)
|
||||||
version_lt() {
|
echo -e "\n[1/7] 正在安装必要环境依赖 (curl, jq, cron, procps, python3)..."
|
||||||
test "$(printf '%s\n' "$1" "$2" | sort -V | head -n 1)" = "$1" && test "$1" != "$2"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1. 依赖检查与智能安装 (v3.5.4 兼容性升级: 支持 Alpine, Arch 及更完善的依赖链)
|
# ================== [Legacy: Debian 9 APT 源抢修补丁] ==================
|
||||||
echo -e "\n[1/7] 正在探测并安装基础环境依赖 (curl, jq, cron, procps, python3)..."
|
if [ -f /etc/debian_version ] && grep -q -E "^9\." /etc/debian_version; then
|
||||||
|
echo -e "\033[33m⚠️ 检测到 Debian 9 (Stretch),正在抢修已停用的 APT 档案馆源...\033[0m"
|
||||||
# 定义必须检测的核心命令
|
echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list
|
||||||
REQUIRED_CMDS=("curl" "jq" "crontab" "pgrep" "python3")
|
echo "deb http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list
|
||||||
MISSING_CMDS=()
|
sed -i '/stretch-updates/d' /etc/apt/sources.list
|
||||||
|
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until
|
||||||
# 基础探测:预检查缺失的命令
|
fi
|
||||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
# =======================================================================
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
MISSING_CMDS+=("$cmd")
|
if [ -f /etc/debian_version ]; then
|
||||||
fi
|
apt-get update -y >/dev/null 2>&1
|
||||||
done
|
apt-get install -y curl jq cron procps python3 >/dev/null 2>&1
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
# 如果有缺失,执行智能安装逻辑
|
yum install -y curl jq cronie procps-ng python3 >/dev/null 2>&1
|
||||||
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
systemctl enable crond && systemctl start crond
|
||||||
echo "⏳ 发现缺失依赖: ${MISSING_CMDS[*]},正在尝试自动补齐..."
|
else
|
||||||
|
echo "⚠️ 未知系统,请确保已手动安装 curl, jq, pgrep 和 python3"
|
||||||
# 嗅探包管理器
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
|
||||||
# Debian / Ubuntu 系列
|
|
||||||
apt-get update -y >/dev/null 2>&1
|
|
||||||
# [v3.6.3 抽脂级优化] 注入 --no-install-recommends 拒绝捆绑销售,大幅节省磁盘与内存
|
|
||||||
apt-get install -y --no-install-recommends curl jq cron procps python3 >/dev/null 2>&1
|
|
||||||
systemctl enable cron >/dev/null 2>&1 && systemctl start cron >/dev/null 2>&1
|
|
||||||
|
|
||||||
elif command -v yum >/dev/null 2>&1 || command -v dnf >/dev/null 2>&1; then
|
|
||||||
# RHEL / CentOS / AlmaLinux 系列
|
|
||||||
PKG_MGR="yum"
|
|
||||||
OPT_ARGS=""
|
|
||||||
if command -v dnf >/dev/null 2>&1; then
|
|
||||||
PKG_MGR="dnf"
|
|
||||||
# [v3.6.3 抽脂级优化] 强行关闭 DNF 的弱依赖拉取
|
|
||||||
OPT_ARGS="--setopt=install_weak_deps=False"
|
|
||||||
fi
|
|
||||||
$PKG_MGR install -y $OPT_ARGS curl jq cronie procps-ng python3 >/dev/null 2>&1
|
|
||||||
systemctl enable crond >/dev/null 2>&1 && systemctl start crond >/dev/null 2>&1
|
|
||||||
|
|
||||||
elif command -v apk >/dev/null 2>&1; then
|
|
||||||
# Alpine 本身就是极致精简,无需特殊参数
|
|
||||||
echo "Alpine 探测到系统类型为 Alpine Linux,正在执行轻量级安装..."
|
|
||||||
apk add --no-cache curl jq dcron procps python3 bash >/dev/null 2>&1
|
|
||||||
mkdir -p /var/spool/cron/crontabs
|
|
||||||
rc-update add crond default >/dev/null 2>&1
|
|
||||||
service crond start >/dev/null 2>&1
|
|
||||||
|
|
||||||
elif command -v pacman >/dev/null 2>&1; then
|
|
||||||
# Arch Linux 系列
|
|
||||||
pacman -Sy --noconfirm curl jq cronie procps-ng python >/dev/null 2>&1
|
|
||||||
mkdir -p /root/.cache/crontab 2>/dev/null
|
|
||||||
systemctl enable cronie >/dev/null 2>&1 && systemctl start cronie >/dev/null 2>&1
|
|
||||||
|
|
||||||
else
|
|
||||||
# 无法识别的系统:退出并给出清晰的引导信息 (同步更新防捆绑参数)
|
|
||||||
echo -e "\033[31m❌ 自动安装失败:系统未知的包管理器。\033[0m"
|
|
||||||
echo -e "\033[33m⚠️ 请根据您的操作系统,手动执行以下安装命令后重新运行本脚本:\033[0m"
|
|
||||||
echo -e " Debian/Ubuntu: \033[36mapt-get update && apt-get install -y --no-install-recommends curl jq cron procps python3\033[0m"
|
|
||||||
echo -e " CentOS/RHEL: \033[36myum install -y curl jq cronie procps-ng python3\033[0m"
|
|
||||||
echo -e " Alpine Linux: \033[36mapk add --no-cache curl jq dcron procps python3 bash\033[0m"
|
|
||||||
echo -e " Arch Linux: \033[36mpacman -Sy curl jq cronie procps-ng python\033[0m"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 安装后二次复检
|
|
||||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
echo -e "\033[31m❌ 致命错误:核心命令 '$cmd' 仍未找到!\033[0m"
|
|
||||||
echo -e "这通常是因为您的系统源配置错误或缺失基础组件库导致。"
|
|
||||||
echo -e "请手动修复您的包管理器源,或联系 VPS 供应商重新格式化系统。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
echo -e "\033[32m✅ 基础环境检测通过。\033[0m"
|
|
||||||
|
|
||||||
# 2. 交互式引导与动态地图解析 (v3.0 全球网络)
|
# 2. 交互式引导与动态地图解析 (v3.0 全球网络)
|
||||||
echo -e "\n[2/7] 正在连线云端,拉取全球节点地图..."
|
echo -e "\n[2/7] 正在连线云端,拉取全球节点地图..."
|
||||||
@@ -115,73 +48,56 @@ if [ ! -s "/tmp/map.json" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ==========================================================
|
echo -e "\n请选择操作:"
|
||||||
# [v3.6.0 核心] 拦截静默 OTA 升级模式 (强行接管执行流,跳过人工交互)
|
echo " 1) 🚀 部署边缘节点 (进入全球节点配置)"
|
||||||
# ==========================================================
|
echo " 2) 🗑️ 一键卸载 IP-Sentinel"
|
||||||
if [ "$SILENT_OTA" == "true" ]; then
|
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
||||||
echo -e "\n⏳ [OTA] 静默升级指令已确认,正在剥离控制台交互..."
|
|
||||||
ACTION_CHOICE=1
|
|
||||||
UPGRADE_MODE="true"
|
|
||||||
KEEP_LOGS="true"
|
|
||||||
source "$CONFIG_FILE"
|
|
||||||
else
|
|
||||||
echo -e "\n请选择操作:"
|
|
||||||
echo " 1) 🚀 部署边缘节点 (进入全球节点配置)"
|
|
||||||
echo " 2) 🗑️ 一键卸载 IP-Sentinel"
|
|
||||||
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
|
||||||
|
|
||||||
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定
|
if [ "$ACTION_CHOICE" == "2" ]; then
|
||||||
ACTION_CHOICE=${ACTION_CHOICE:-1}
|
echo -e "\n⏳ 正在拉取卸载程序..."
|
||||||
|
curl -sL "${REPO_RAW_URL}/core/uninstall.sh" -o "/tmp/ip_uninstall.sh"
|
||||||
if [ "$ACTION_CHOICE" == "2" ]; then
|
chmod +x "/tmp/ip_uninstall.sh"
|
||||||
echo -e "\n⏳ 正在拉取卸载程序..."
|
bash "/tmp/ip_uninstall.sh"
|
||||||
curl -sL "${REPO_RAW_URL}/core/uninstall.sh" -o "/tmp/ip_uninstall.sh"
|
rm -f "/tmp/ip_uninstall.sh"
|
||||||
chmod +x "/tmp/ip_uninstall.sh"
|
exit 0
|
||||||
bash "/tmp/ip_uninstall.sh"
|
|
||||||
rm -f "/tmp/ip_uninstall.sh"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
|
||||||
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
|
|
||||||
# ====================================================================
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
||||||
|
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 优化: 安装前环境纯净度清理] ==================
|
# ================== [v3.1.1/v3.2.2 优化: 安装前环境纯净度清理] ==================
|
||||||
echo -e "\n⏳ 正在清理旧版守护进程与冗余任务..."
|
echo -e "\n⏳ 正在清理旧版守护进程与冗余任务..."
|
||||||
# [新增] 优雅停止 Systemd 服务,防止代码替换时引发无限复活风暴
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
systemctl stop ip-sentinel-runner.timer ip-sentinel-updater.timer ip-sentinel-report.timer ip-sentinel-agent-daemon.service >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 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
|
||||||
pkill -9 -f "runner.sh" >/dev/null 2>&1 || true
|
pkill -9 -f "runner.sh" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
# 2. 清除系统定时任务 (Cron) 中的旧版条目 (安全容错版)
|
# 2. 清除系统定时任务 (Cron) 中的旧版条目
|
||||||
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_clean || true
|
if crontab -l >/dev/null 2>&1; then
|
||||||
[ -f /tmp/cron_clean ] && crontab /tmp/cron_clean 2>/dev/null
|
crontab -l | grep -v "ip_sentinel" > /tmp/cron_clean
|
||||||
rm -f /tmp/cron_clean
|
crontab /tmp/cron_clean
|
||||||
|
rm -f /tmp/cron_clean
|
||||||
|
fi
|
||||||
|
|
||||||
# 3. 抹除旧版核心代码,杜绝代码冲突 (根据模式分流)
|
# 3. 抹除旧版核心代码,杜绝代码冲突 (根据模式分流)
|
||||||
if [ "$UPGRADE_MODE" == "true" ]; then
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
@@ -207,23 +123,9 @@ echo -e "\033[32m✅ 环境清理完毕,幽灵进程已肃清!\033[0m"
|
|||||||
# ==========================================================
|
# ==========================================================
|
||||||
if [ "$UPGRADE_MODE" == "false" ]; then
|
if [ "$UPGRADE_MODE" == "false" ]; then
|
||||||
|
|
||||||
# 📍 动态零级菜单:战区(大洲)选择
|
# 📍 动态一级菜单:国家选择
|
||||||
echo -e "\n\033[36m📍 【第零级】请选择目标战区 (Continent):\033[0m"
|
echo -e "\n\033[36m📍 【第一级】请选择目标国家/地区:\033[0m"
|
||||||
jq -r '.continents[] | "\(.id)|\(.name)"' /tmp/map.json > /tmp/continents.txt
|
jq -r '.countries[] | "\(.id)|\(.name)|\(.keyword_file)"' /tmp/map.json > /tmp/countries.txt
|
||||||
i=1; CONT_MAP=()
|
|
||||||
while IFS="|" read -r cont_id cont_name; do
|
|
||||||
echo " $i) $cont_name"
|
|
||||||
CONT_MAP[$i]="$cont_id"
|
|
||||||
((i++))
|
|
||||||
done < /tmp/continents.txt
|
|
||||||
|
|
||||||
read -p "请输入选择 [1-$((i-1))] (默认1): " CONT_SEL
|
|
||||||
CONT_SEL=${CONT_SEL:-1}
|
|
||||||
CONT_ID="${CONT_MAP[$CONT_SEL]}"
|
|
||||||
|
|
||||||
# 📍 动态一级菜单:国家选择 (基于选中战区)
|
|
||||||
echo -e "\n\033[36m📍 【第一级】正在检索 [$CONT_ID] 战区下的国家/地区...\033[0m"
|
|
||||||
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | \"\(.id)|\(.name)|\(.keyword_file)\"" /tmp/map.json > /tmp/countries.txt
|
|
||||||
i=1; COUNTRY_MAP=(); KEYWORD_MAP=()
|
i=1; COUNTRY_MAP=(); KEYWORD_MAP=()
|
||||||
while IFS="|" read -r c_id c_name k_file; do
|
while IFS="|" read -r c_id c_name k_file; do
|
||||||
echo " $i) $c_name"
|
echo " $i) $c_name"
|
||||||
@@ -238,9 +140,9 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
KEYWORD_FILE="${KEYWORD_MAP[$C_SEL]}"
|
KEYWORD_FILE="${KEYWORD_MAP[$C_SEL]}"
|
||||||
REGION_CODE="$COUNTRY_ID" # 兼容旧版的 config.conf
|
REGION_CODE="$COUNTRY_ID" # 兼容旧版的 config.conf
|
||||||
|
|
||||||
# 📍 动态二级菜单:省/州选择 (基于选中战区和国家)
|
# 📍 动态二级菜单:省/州选择
|
||||||
echo -e "\n\033[36m📍 【第二级】正在检索 [$COUNTRY_ID] 的行政区数据...\033[0m"
|
echo -e "\n\033[36m📍 【第二级】正在检索 [$COUNTRY_ID] 的行政区数据...\033[0m"
|
||||||
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/states.txt
|
jq -r ".countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/states.txt
|
||||||
STATE_COUNT=$(wc -l < /tmp/states.txt)
|
STATE_COUNT=$(wc -l < /tmp/states.txt)
|
||||||
|
|
||||||
if [ "$STATE_COUNT" -eq 1 ]; then
|
if [ "$STATE_COUNT" -eq 1 ]; then
|
||||||
@@ -258,30 +160,28 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
STATE_ID="${STATE_MAP[$S_SEL]}"
|
STATE_ID="${STATE_MAP[$S_SEL]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 📍 动态三级菜单:城市选择 (基于战区、国家、州三层过滤)
|
# 📍 动态三级菜单:城市选择
|
||||||
echo -e "\n\033[36m📍 【第三级】请锁定具体城市节点:\033[0m"
|
echo -e "\n\033[36m📍 【第三级】请锁定具体城市节点:\033[0m"
|
||||||
jq -r ".continents[] | select(.id==\"$CONT_ID\") | .countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | select(.id==\"$STATE_ID\") | .cities[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/cities.txt
|
jq -r ".countries[] | select(.id==\"$COUNTRY_ID\") | .states[] | select(.id==\"$STATE_ID\") | .cities[] | \"\(.id)|\(.name)\"" /tmp/map.json > /tmp/cities.txt
|
||||||
CITY_COUNT=$(wc -l < /tmp/cities.txt)
|
CITY_COUNT=$(wc -l < /tmp/cities.txt)
|
||||||
|
|
||||||
if [ "$CITY_COUNT" -eq 1 ]; then
|
if [ "$CITY_COUNT" -eq 1 ]; then
|
||||||
IFS="|" read -r CITY_ID CITY_NAME < /tmp/cities.txt
|
IFS="|" read -r CITY_ID CITY_NAME < /tmp/cities.txt
|
||||||
echo -e "\033[32m💡 该区域下仅有单一城市 [$CITY_NAME],已自动锁定。\033[0m"
|
echo -e "\033[32m💡 该区域下仅有单一城市 [$CITY_NAME],已自动锁定。\033[0m"
|
||||||
else
|
else
|
||||||
i=1; CITY_MAP=(); CITY_NAME_MAP=()
|
i=1; CITY_MAP=()
|
||||||
while IFS="|" read -r c_id c_name; do
|
while IFS="|" read -r c_id c_name; do
|
||||||
echo " $i) $c_name"
|
echo " $i) $c_name"
|
||||||
CITY_MAP[$i]="$c_id"
|
CITY_MAP[$i]="$c_id"
|
||||||
CITY_NAME_MAP[$i]="$c_name"
|
|
||||||
((i++))
|
((i++))
|
||||||
done < /tmp/cities.txt
|
done < /tmp/cities.txt
|
||||||
read -p "请输入选择 [1-$((i-1))] (默认1): " CI_SEL
|
read -p "请输入选择 [1-$((i-1))] (默认1): " CI_SEL
|
||||||
CI_SEL=${CI_SEL:-1}
|
CI_SEL=${CI_SEL:-1}
|
||||||
CITY_ID="${CITY_MAP[$CI_SEL]}"
|
CITY_ID="${CITY_MAP[$CI_SEL]}"
|
||||||
CITY_NAME="${CITY_NAME_MAP[$CI_SEL]}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 清理临时文件 (增加清理 continents.txt)
|
# 清理临时文件
|
||||||
rm -f /tmp/map.json /tmp/continents.txt /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
|
rm -f /tmp/map.json /tmp/countries.txt /tmp/states.txt /tmp/cities.txt
|
||||||
|
|
||||||
# 本地工作目录初始化 (支持 v3.0 的深度层级)
|
# 本地工作目录初始化 (支持 v3.0 的深度层级)
|
||||||
mkdir -p "${INSTALL_DIR}/core"
|
mkdir -p "${INSTALL_DIR}/core"
|
||||||
@@ -289,66 +189,46 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
mkdir -p "${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}"
|
mkdir -p "${INSTALL_DIR}/data/regions/${COUNTRY_ID}/${STATE_ID}"
|
||||||
mkdir -p "${INSTALL_DIR}/logs"
|
mkdir -p "${INSTALL_DIR}/logs"
|
||||||
|
|
||||||
# 3. 功能模块前置开关 (v3.5.3 默认全量加载,后续经由 TG 动态启停)
|
# 3. 功能模块前置开关 (按需加载)
|
||||||
echo -e "\n[3/7] 正在初始化养护模块 (默认全量部署,支持 TG 远程动态启停)..."
|
echo -e "\n[3/7] 请选择需要开启的养护模块 (按需开启,节省资源):"
|
||||||
|
echo " 1) 📍 仅开启 [Google 区域纠偏] (默认,适合流媒体解锁机位漂移)"
|
||||||
|
echo " 2) 🛡️ 仅开启 [IP 信用净化] (适合高风险机房 IP 降低 Scamalytics 分数)"
|
||||||
|
echo " 3) 🔥 双管齐下 (同时开启以上两项)"
|
||||||
|
read -p "请输入选择 [1-3] (默认1): " MODULE_CHOICE
|
||||||
|
|
||||||
ENABLE_GOOGLE="true"
|
ENABLE_GOOGLE="true"
|
||||||
ENABLE_TRUST="true"
|
ENABLE_TRUST="false"
|
||||||
|
case ${MODULE_CHOICE:-1} in
|
||||||
|
2) ENABLE_GOOGLE="false"; ENABLE_TRUST="true" ;;
|
||||||
|
3) ENABLE_GOOGLE="true"; ENABLE_TRUST="true" ;;
|
||||||
|
*) ENABLE_GOOGLE="true"; ENABLE_TRUST="false" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# 4. 接入 Master 中枢配置
|
# 4. 接入 Master 中枢配置
|
||||||
echo -e "\n[4/7] 是否接入 Master 司令部进行远程联控? (y/n)"
|
echo -e "\n[4/7] 是否接入 Master 司令部?(需要配置与主控相同的 TG 机器人) (y/n)"
|
||||||
read -p "请输入选择 [y/n] (默认n): " TG_CHOICE
|
read -p "请输入选择 [y/n] (默认n): " TG_CHOICE
|
||||||
TG_TOKEN=""
|
TG_TOKEN=""
|
||||||
CHAT_ID=""
|
CHAT_ID=""
|
||||||
AGENT_PORT="9527"
|
AGENT_PORT="9527"
|
||||||
if [[ "$TG_CHOICE" =~ ^[Yy]$ ]]; then
|
if [[ "$TG_CHOICE" =~ ^[Yy]$ ]]; then
|
||||||
echo -e "\n请选择中枢接入模式 (推荐私有部署,支持后续 OTA 远程静默升级):"
|
echo -e "\n\033[33m💡 提示:您可以选择使用自己的机器人,或者直接回车使用官方公共机器人。\033[0m"
|
||||||
echo " 1) 🛡️ 私有独立中枢 (需提供自建 Bot Token,推荐)"
|
echo -e "\033[33m⚠️ 注意:若使用官方机器人,请务必先在 TG 中关注 @OmniBeacon_bot 并发送 /start\033[0m"
|
||||||
echo " 2) ☁️ 官方公共网关 (@OmniBeacon_bot,新手免配置)"
|
|
||||||
read -p "请输入选择 [1-2] (默认1): " MASTER_TYPE
|
|
||||||
MASTER_TYPE=${MASTER_TYPE:-1}
|
|
||||||
|
|
||||||
if [ "$MASTER_TYPE" == "2" ]; then
|
read -p "请输入您的 Telegram Bot Token (回车使用官方默认): " USER_TOKEN
|
||||||
|
|
||||||
|
if [ -z "$USER_TOKEN" ]; then
|
||||||
TG_TOKEN="OFFICIAL_GATEWAY_MODE"
|
TG_TOKEN="OFFICIAL_GATEWAY_MODE"
|
||||||
TG_API_URL="https://omni-gateway.samanthaestime296.workers.dev"
|
TG_API_URL="https://omni-gateway.samanthaestime296.workers.dev"
|
||||||
ENABLE_OTA="false"
|
|
||||||
echo -e "\033[32m✅ 已自动连接官方安全网关 (@OmniBeacon_bot)。\033[0m"
|
echo -e "\033[32m✅ 已自动连接官方安全网关 (@OmniBeacon_bot)。\033[0m"
|
||||||
echo -e "\033[33m👉 请确保您已在 TG 中关注官方机器人并发送过 /start,否则将无法接收消息。\033[0m"
|
echo -e "\033[33m👉 请确保您已关注官方机器人并发送过 /start,否则将无法接收消息。\033[0m"
|
||||||
# [v3.6.0 安全熔断]
|
|
||||||
echo -e "\n\033[33m⚠️ 【安全熔断提示】\033[0m"
|
|
||||||
echo -e "\033[33m由于您使用了官方公共网关,为防止潜在的滥用或供应链风险,本节点的 [OTA 远程升级] 权限已被系统底层强制禁用。\033[0m"
|
|
||||||
echo -e "\033[33m💡 若未来需要启用 OTA,请自建私有中枢后重新部署本节点。\033[0m"
|
|
||||||
else
|
else
|
||||||
# [v3.6.0 优化] 使用 OSC 8 终端超链接协议,实现“点击即打开”的极客交互
|
|
||||||
echo -e "\n\033[36m📘 私有 Bot 创建教程: \033[4m\033]8;;https://blog.iot-architect.com/engineering-practice/create-private-telegram-bot-via-botfather/\033\\👉 [点击此处直接在浏览器中打开] 👈\033]8;;\033\\\033[0m"
|
|
||||||
echo -e "\033[90m (若您的终端较老不支持点击,请手动复制: https://blog.iot-architect.com/engineering-practice/create-private-telegram-bot-via-botfather/ )\033[0m"
|
|
||||||
read -p "请输入您的私有 Telegram Bot Token: " USER_TOKEN
|
|
||||||
|
|
||||||
# 🛡️ 核心防误触修复:拦截空回车或粘贴换行导致的跳过 Bug
|
|
||||||
while [ -z "$USER_TOKEN" ]; do
|
|
||||||
read -p "⚠️ Token 不能为空,请重新输入您的 Bot Token: " USER_TOKEN
|
|
||||||
done
|
|
||||||
|
|
||||||
TG_TOKEN="$USER_TOKEN"
|
TG_TOKEN="$USER_TOKEN"
|
||||||
TG_API_URL="https://api.telegram.org/bot${TG_TOKEN}/sendMessage"
|
TG_API_URL="https://api.telegram.org/bot${TG_TOKEN}/sendMessage"
|
||||||
echo -e "\033[32m✅ 已记录您的私有机器人 Token。\033[0m"
|
echo -e "\033[32m✅ 已记录您的私有机器人 Token。\033[0m"
|
||||||
|
|
||||||
# [v3.6.0] 私有模式开放 OTA 授权向导
|
|
||||||
echo -e "\n\033[36m[4.1/7] OTA 远程静默升级授权\033[0m"
|
|
||||||
echo -e "💡 开启后,您可以在 TG 面板一键将本节点热更新至最新版本。"
|
|
||||||
read -p "是否允许本节点接收 OTA 升级指令?(y/n, 默认y): " OTA_CHOICE
|
|
||||||
if [[ "$OTA_CHOICE" =~ ^[Nn]$ ]]; then
|
|
||||||
ENABLE_OTA="false"
|
|
||||||
echo -e "🛡️ \033[33m已关闭 OTA 权限,本节点未来将只能通过 SSH 手动升级。\033[0m"
|
|
||||||
else
|
|
||||||
ENABLE_OTA="true"
|
|
||||||
echo -e "✅ \033[32m已开启 OTA 权限,核按钮已挂载至您的私有中枢。\033[0m"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n\033[33m💡 提示:如果您不知道下方自己的 Chat ID 是什么,可以关注 @userinfobot 获取。\033[0m"
|
echo -e "\033[33m💡 提示:如果您不知道自己的 Chat ID,可以关注 @userinfobot 获取。\033[0m"
|
||||||
echo -e "\033[36m📘 查看图文教程: \033[4m\033]8;;https://blog.iot-architect.com/engineering-practice/get-telegram-personal-id-via-userinfobot/\033\\👉 [点击此处直接在浏览器中打开] 👈\033]8;;\033\\\033[0m"
|
read -p "请输入你的 Chat ID (与主控一致): " CHAT_ID
|
||||||
echo -e "\033[90m (若您的终端较老不支持点击,请手动复制: https://blog.iot-architect.com/engineering-practice/get-telegram-personal-id-via-userinfobot/ )\033[0m"
|
|
||||||
read -p "请输入你的 Chat ID (必须准确,否则无法联控): " CHAT_ID
|
|
||||||
|
|
||||||
# ================== [v3.0.3 变更: 智能随机高位端口生成系统] ==================
|
# ================== [v3.0.3 变更: 智能随机高位端口生成系统] ==================
|
||||||
echo -e "\n\033[36m[4.2/7] 正在构建 Webhook 安全通信隧道...\033[0m"
|
echo -e "\n\033[36m[4.2/7] 正在构建 Webhook 安全通信隧道...\033[0m"
|
||||||
@@ -438,53 +318,13 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ================== [v3.3.1 核心重构: 身份剥离与双栈实弹嗅探] ==================
|
# 终极修复:为 IPv6 自动穿上防护装甲(方括号),解决 Master 拼接 URL 报错问题
|
||||||
# 1. 固化对外通讯身份 (自动穿透方括号护甲)
|
|
||||||
if [[ "$PUBLIC_IP" == *":"* ]] && [[ "$PUBLIC_IP" != *"["* ]]; then
|
if [[ "$PUBLIC_IP" == *":"* ]] && [[ "$PUBLIC_IP" != *"["* ]]; then
|
||||||
SAFE_PUBLIC_IP="[${PUBLIC_IP}]"
|
BIND_IP="[${PUBLIC_IP}]"
|
||||||
else
|
else
|
||||||
SAFE_PUBLIC_IP="$PUBLIC_IP"
|
BIND_IP="$PUBLIC_IP"
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. 实弹打靶测试 (NAT 环境嗅探与双栈自适应)
|
|
||||||
echo -n "🕵️ 正在进行出站链路试射 (NAT环境与双栈嗅探)..."
|
|
||||||
RAW_TEST_IP=$(echo "$SAFE_PUBLIC_IP" | tr -d '[]')
|
|
||||||
|
|
||||||
# 智能切换靶机:V6 机器打 Cloudflare V6 节点,V4 机器打 1.1.1.1
|
|
||||||
if [[ "$RAW_TEST_IP" == *":"* ]]; then
|
|
||||||
TEST_TARGET="https://[2606:4700:4700::1111]"
|
|
||||||
else
|
|
||||||
TEST_TARGET="https://1.1.1.1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 执行实弹试射
|
|
||||||
if curl --interface "$RAW_TEST_IP" -sI -m 3 "$TEST_TARGET" >/dev/null 2>&1; then
|
|
||||||
echo -e " \033[32m✅ 原生直连,物理网卡死锁已激活。\033[0m"
|
|
||||||
BIND_IP="$SAFE_PUBLIC_IP"
|
|
||||||
else
|
|
||||||
echo -e " \033[33m⚠️ 发现 NAT/虚拟路由架构,自动卸除网卡枷锁,交由内核路由。\033[0m"
|
|
||||||
BIND_IP=""
|
|
||||||
fi
|
|
||||||
echo -e "\033[32m✅ 哨兵对外联络点已永久锁定至: $SAFE_PUBLIC_IP\033[0m"
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
# ================== [v3.5.2 新增: 节点不可变主键与展示别名] ==================
|
|
||||||
IP_HASH=$(echo "${SAFE_PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
|
|
||||||
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${IP_HASH}"
|
|
||||||
NODE_ALIAS="$NODE_NAME"
|
|
||||||
|
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
|
||||||
echo -e "\n\033[36m[4.8/7] 节点展示别名设定 (用于面板友好显示)...\033[0m"
|
|
||||||
echo -e "💡 系统底层的不可变主键为: \033[33m${NODE_NAME}\033[0m"
|
|
||||||
read -p "请输入节点展示别名 (如'纽约机房', 回车使用默认): " CUSTOM_ALIAS
|
|
||||||
|
|
||||||
if [ -n "$CUSTOM_ALIAS" ]; then
|
|
||||||
# 🛡️ 强制字符清洗:防御 Shell 注入,并限制长度防刷屏
|
|
||||||
NODE_ALIAS=$(echo "$CUSTOM_ALIAS" | tr -d '"'\''\`\$\|&;<>\n\r' | cut -c 1-20)
|
|
||||||
[ -z "$NODE_ALIAS" ] && NODE_ALIAS="$NODE_NAME"
|
|
||||||
fi
|
|
||||||
echo -e "✅ 已锁定节点展示别名: \033[32m$NODE_ALIAS\033[0m"
|
|
||||||
fi
|
fi
|
||||||
|
echo -e "\033[32m✅ 哨兵锚点已永久锁定至: $BIND_IP\033[0m"
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
# 5. 远程拉取冷数据并解析固化
|
# 5. 远程拉取冷数据并解析固化
|
||||||
@@ -504,10 +344,9 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
LANG_PARAMS=$(jq -r '.google_module.lang_params' "$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")
|
VALID_URL_SUFFIX=$(jq -r '.google_module.valid_url_suffix' "$REGION_JSON_FILE")
|
||||||
|
|
||||||
# 写入本地静态配置文件 (v3.4.0 引入版本锚点)
|
# 写入本地静态配置文件
|
||||||
cat > "$CONFIG_FILE" << EOF
|
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"
|
||||||
@@ -526,17 +365,9 @@ 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.3.1修改: 双核身份剥离配置]
|
# [v3.0.1新增修改 2: 网络栈锚点锁定配置,供其他脚本读取]
|
||||||
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"
|
|
||||||
|
|
||||||
# [v3.6.0新增: OTA 权限标识]
|
|
||||||
ENABLE_OTA="$ENABLE_OTA"
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# ================== [v3.0.3 变更: 敏感配置文件权限收敛] ==================
|
# ================== [v3.0.3 变更: 敏感配置文件权限收敛] ==================
|
||||||
@@ -546,69 +377,6 @@ EOF
|
|||||||
fi
|
fi
|
||||||
# 🛑 拦截块结束 (全套交互配置跳过完毕)
|
# 🛑 拦截块结束 (全套交互配置跳过完毕)
|
||||||
|
|
||||||
# ================== [v3.3.1 核心修复: 老节点配置无损热迁移] ==================
|
|
||||||
if [ "$UPGRADE_MODE" == "true" ]; then
|
|
||||||
if ! grep -q "PUBLIC_IP=" "$CONFIG_FILE"; then
|
|
||||||
echo -e "\n🔄 [平滑迁移] 正在对老节点进行 v3.3.1 双核身份架构升级..."
|
|
||||||
|
|
||||||
# 重新抓取公网面孔 (应对老节点 BIND_IP 可能已被手动清空的情况)
|
|
||||||
MIGRATE_IP=$(curl -${IP_PREF:-4} -s -m 5 api.ip.sb/ip | tr -d '[:space:]')
|
|
||||||
[[ "$MIGRATE_IP" == *":"* ]] && [[ "$MIGRATE_IP" != *"["* ]] && MIGRATE_IP="[${MIGRATE_IP}]"
|
|
||||||
|
|
||||||
echo -n "🕵️ 正在进行补发链路试射 (NAT与双栈嗅探)..."
|
|
||||||
RAW_TEST_IP=$(echo "$MIGRATE_IP" | tr -d '[]')
|
|
||||||
if [[ "$RAW_TEST_IP" == *":"* ]]; then
|
|
||||||
TEST_TARGET="https://[2606:4700:4700::1111]"
|
|
||||||
else
|
|
||||||
TEST_TARGET="https://1.1.1.1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if curl --interface "$RAW_TEST_IP" -sI -m 3 "$TEST_TARGET" >/dev/null 2>&1; then
|
|
||||||
echo -e " \033[32m✅ 原生直连,网卡死锁已继承。\033[0m"
|
|
||||||
NEW_BIND_IP="$MIGRATE_IP"
|
|
||||||
else
|
|
||||||
echo -e " \033[33m⚠️ 发现 NAT 架构,已自动卸除老版本的物理枷锁。\033[0m"
|
|
||||||
NEW_BIND_IP=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 动态修改旧配置文件 (更新 BIND_IP,追加 PUBLIC_IP)
|
|
||||||
sed -i "s/^BIND_IP=.*/BIND_IP=\"$NEW_BIND_IP\"/" "$CONFIG_FILE"
|
|
||||||
echo "PUBLIC_IP=\"$MIGRATE_IP\"" >> "$CONFIG_FILE"
|
|
||||||
|
|
||||||
# 刷新当前安装脚本的环境变量,防止底部代码报错
|
|
||||||
SAFE_PUBLIC_IP="$MIGRATE_IP"
|
|
||||||
BIND_IP="$NEW_BIND_IP"
|
|
||||||
else
|
|
||||||
# 如果是未来再升级,配置文件已是最新,直接提取变量供安装脚本尾部使用
|
|
||||||
SAFE_PUBLIC_IP=$(grep "^PUBLIC_IP=" "$CONFIG_FILE" | cut -d'"' -f2)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# [v3.5.2 热修复] 兼容老版本没有 NODE_NAME 和 NODE_ALIAS 的情况,无损补齐
|
|
||||||
if ! grep -q "^NODE_NAME=" "$CONFIG_FILE"; then
|
|
||||||
TMP_HASH=$(echo "${SAFE_PUBLIC_IP:-127.0.0.1}" | md5sum | cut -c 1-4 | tr 'a-z' 'A-Z')
|
|
||||||
NODE_NAME="$(hostname | tr -cd 'a-zA-Z0-9' | cut -c 1-10)-${TMP_HASH}"
|
|
||||||
NODE_ALIAS="$NODE_NAME"
|
|
||||||
echo "NODE_NAME=\"$NODE_NAME\"" >> "$CONFIG_FILE"
|
|
||||||
echo "NODE_ALIAS=\"$NODE_ALIAS\"" >> "$CONFIG_FILE"
|
|
||||||
else
|
|
||||||
NODE_NAME=$(grep "^NODE_NAME=" "$CONFIG_FILE" | cut -d'"' -f2)
|
|
||||||
NODE_ALIAS=$(grep "^NODE_ALIAS=" "$CONFIG_FILE" | cut -d'"' -f2)
|
|
||||||
if [ -z "$NODE_ALIAS" ]; then
|
|
||||||
NODE_ALIAS="$NODE_NAME"
|
|
||||||
echo "NODE_ALIAS=\"$NODE_ALIAS\"" >> "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# [v3.6.0 热修复] 兼容老版本没有 ENABLE_OTA 的情况,无损补齐默认关闭以防滥用
|
|
||||||
if ! grep -q "^ENABLE_OTA=" "$CONFIG_FILE"; then
|
|
||||||
echo "ENABLE_OTA=\"false\"" >> "$CONFIG_FILE"
|
|
||||||
ENABLE_OTA="false"
|
|
||||||
else
|
|
||||||
ENABLE_OTA=$(grep "^ENABLE_OTA=" "$CONFIG_FILE" | cut -d'"' -f2)
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
# 6. 拉取全套组件 (按需下载,绝不浪费空间)
|
# 6. 拉取全套组件 (按需下载,绝不浪费空间)
|
||||||
echo -e "\n[6/7] 正在根据模块开关部署核心引擎与热数据..."
|
echo -e "\n[6/7] 正在根据模块开关部署核心引擎与热数据..."
|
||||||
# 确保目录在升级模式下也能被正确建立
|
# 确保目录在升级模式下也能被正确建立
|
||||||
@@ -642,197 +410,64 @@ fi
|
|||||||
chmod +x ${INSTALL_DIR}/core/*.sh
|
chmod +x ${INSTALL_DIR}/core/*.sh
|
||||||
|
|
||||||
# 7. 配置系统定时任务 (高频调度与看门狗)
|
# 7. 配置系统定时任务 (高频调度与看门狗)
|
||||||
echo -e "\n[7/7] 正在注入系统守护进程与调度器..."
|
echo -e "\n[7/7] 正在注入系统定时任务与看门狗进程..."
|
||||||
|
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_backup
|
||||||
|
|
||||||
|
# 核心养护模块: 每 30 分钟触发一次
|
||||||
|
echo "*/30 * * * * ${INSTALL_DIR}/core/runner.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
||||||
|
# 养料更新模块: (v3.3.0升级) 每天凌晨 3 点触发,由中枢自动进行分频调度
|
||||||
|
echo "0 3 * * * ${INSTALL_DIR}/core/updater.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
||||||
|
|
||||||
# [v3.3.0 新增] 初始化 UA 指纹库更新时间戳,确立 30 天滚动周期的计算锚点
|
# [v3.3.0 新增] 初始化 UA 指纹库更新时间戳,确立 30 天滚动周期的计算锚点
|
||||||
echo $(date +%s) > "${INSTALL_DIR}/core/.ua_last_update"
|
echo $(date +%s) > "${INSTALL_DIR}/core/.ua_last_update"
|
||||||
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
# 如果配置了联控,启动 Webhook 与战报任务
|
||||||
echo "💡 检测到 Systemd 环境,正在部署原生守护服务..."
|
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
||||||
|
# 每天早上 8 点发送昨天的统计战报
|
||||||
|
echo "0 8 * * * ${INSTALL_DIR}/core/tg_report.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
||||||
|
|
||||||
# 1. Runner 核心养护模块服务与定时器
|
# [v3.0.1新增修改 3: 删除原来的 curl 取 IP,直接使用我们上方锁定的 BIND_IP]
|
||||||
cat > /etc/systemd/system/ip-sentinel-runner.service << EOF
|
# 并提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的重复推送
|
||||||
[Unit]
|
# [修复竞态]: 提前写入 IP 缓存,彻底阻断 agent_daemon 首次启动时的抢跑推送
|
||||||
Description=IP-Sentinel Runner Service
|
echo "$BIND_IP" > "${INSTALL_DIR}/core/.last_ip"
|
||||||
After=network.target
|
|
||||||
[Service]
|
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
SyslogIdentifier=ip-sentinel
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/bin/bash ${INSTALL_DIR}/core/runner.sh
|
|
||||||
User=root
|
|
||||||
CPUSchedulingPolicy=idle
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-runner.timer << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Timer for IP-Sentinel Runner Service
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=10
|
|
||||||
OnUnitActiveSec=30min
|
|
||||||
RandomizedDelaySec=180
|
|
||||||
Persistent=true
|
|
||||||
Unit=ip-sentinel-runner.service
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 2. Updater 养料更新模块服务与定时器
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-updater.service << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=IP-Sentinel Updater Service
|
|
||||||
After=network.target
|
|
||||||
[Service]
|
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
SyslogIdentifier=ip-sentinel
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/bin/bash ${INSTALL_DIR}/core/updater.sh
|
|
||||||
User=root
|
|
||||||
CPUSchedulingPolicy=idle
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-updater.timer << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Timer for IP-Sentinel Updater Service
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=*-*-* 03:00:00
|
|
||||||
Persistent=true
|
|
||||||
Unit=ip-sentinel-updater.service
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable --now ip-sentinel-runner.timer ip-sentinel-updater.timer
|
|
||||||
|
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
|
||||||
# 3. TG 战报服务与定时器
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-report.service << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=IP-Sentinel Telegram Report Service
|
|
||||||
After=network.target
|
|
||||||
[Service]
|
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
SyslogIdentifier=ip-sentinel
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/bin/bash ${INSTALL_DIR}/core/tg_report.sh
|
|
||||||
User=root
|
|
||||||
CPUSchedulingPolicy=idle
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-report.timer << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Timer for IP-Sentinel Telegram Report Service
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=*-*-* 08:00:00
|
|
||||||
Unit=ip-sentinel-report.service
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 4. [排雷修复] Agent Daemon Webhook 监听守护服务 (Type=simple, 常驻执行)
|
|
||||||
cat > /etc/systemd/system/ip-sentinel-agent-daemon.service << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=IP-Sentinel Agent Daemon Service
|
|
||||||
After=network.target
|
|
||||||
[Service]
|
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
SyslogIdentifier=ip-sentinel
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/bin/bash ${INSTALL_DIR}/core/agent_daemon.sh
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
User=root
|
|
||||||
CPUSchedulingPolicy=idle
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# [修复竞态]: 提前写入公网 IP 缓存,阻断重复推送
|
|
||||||
echo "$SAFE_PUBLIC_IP" > "${INSTALL_DIR}/core/.last_ip"
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable --now ip-sentinel-report.timer
|
|
||||||
systemctl enable --now ip-sentinel-agent-daemon.service
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "💡 未检测到 Systemd (可能是 Alpine Linux),回退到 Cron 调度模式..."
|
|
||||||
crontab -l 2>/dev/null | grep -v "ip_sentinel" > /tmp/cron_backup || true
|
|
||||||
echo "*/30 * * * * ${INSTALL_DIR}/core/runner.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
|
||||||
echo "0 3 * * * ${INSTALL_DIR}/core/updater.sh >/dev/null 2>&1" >> /tmp/cron_backup
|
|
||||||
|
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
# 双保险守护进程看门狗
|
||||||
echo "0 8 * * * ${INSTALL_DIR}/core/tg_report.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
|
||||||
echo "$SAFE_PUBLIC_IP" > "${INSTALL_DIR}/core/.last_ip"
|
echo "* * * * * 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
|
|
||||||
echo "* * * * * nohup bash ${INSTALL_DIR}/core/agent_daemon.sh >/dev/null 2>&1 &" >> /tmp/cron_backup
|
# 安装时立刻启动一次边缘守护进程
|
||||||
nohup bash "${INSTALL_DIR}/core/agent_daemon.sh" >/dev/null 2>&1 &
|
nohup bash "${INSTALL_DIR}/core/agent_daemon.sh" >/dev/null 2>&1 &
|
||||||
fi
|
|
||||||
[ -f /tmp/cron_backup ] && crontab /tmp/cron_backup 2>/dev/null
|
|
||||||
rm -f /tmp/cron_backup
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ================== [v3.4.0 核心: 状态机驱动的热更新路由] ==================
|
crontab /tmp/cron_backup
|
||||||
|
rm -f /tmp/cron_backup
|
||||||
|
|
||||||
|
# ================== [v3.2.2 优化: 战报通知分流 (注册/升级)] ==================
|
||||||
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
if [[ -n "$TG_TOKEN" ]] && [[ -n "$CHAT_ID" ]]; then
|
||||||
|
NODE_NAME=$(hostname | cut -c 1-15)
|
||||||
# [v3.6.0 核心] 发送携带全套身份属性的注册指令 (追加 ENABLE_OTA 作为第 7 个字段)
|
|
||||||
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${SAFE_PUBLIC_IP}|${AGENT_PORT}|${NODE_ALIAS}|${ENABLE_OTA}"
|
|
||||||
|
|
||||||
if [ "$UPGRADE_MODE" == "true" ]; then
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
# 读取本地老版本号,如果没有则视为远古版本 v3.3.1
|
echo -e "\n📡 正在向指挥部发送升级成功战报..."
|
||||||
OLD_VERSION=$(grep "^AGENT_VERSION=" "$CONFIG_FILE" | cut -d'"' -f2)
|
curl -s -X POST "${TG_API_URL}" \
|
||||||
[ -z "$OLD_VERSION" ] && OLD_VERSION="3.3.1"
|
-d "chat_id=${CHAT_ID}" \
|
||||||
|
-d "parse_mode=Markdown" \
|
||||||
# [路由表 1]: 跨代兼容 (老版本 < v3.3.2)
|
-d "text=✨ *IP-Sentinel 引擎热更新完成!*
|
||||||
# 必须强制下发带有 #REGISTER# 的警告,引导长官重新同步哈希身份
|
📍 节点:\`${NODE_NAME}\`
|
||||||
if version_lt "$OLD_VERSION" "3.3.2"; then
|
🌐 IP:\`${BIND_IP}\`
|
||||||
echo -e "\n📡 [路由枢纽] 正在执行跨代架构重组 (v${OLD_VERSION} -> v${TARGET_VERSION})..."
|
🚀 状态:v3.3.0 OTA 动态活体养护引擎已部署" >/dev/null 2>&1
|
||||||
curl -s -X POST "${TG_API_URL}" \
|
echo -e "\033[32m✅ 升级成功通知已推送到您的 Telegram!\033[0m"
|
||||||
-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
|
else
|
||||||
# [全新安装路由]
|
|
||||||
echo -e "\n📡 正在向指挥部发送注册暗号..."
|
echo -e "\n📡 正在向指挥部发送注册暗号..."
|
||||||
|
# 构造注册暗号 (V3.1.3 协议升级: 携带 REGION_CODE 大区标识)
|
||||||
|
REG_MSG="#REGISTER#|${REGION_CODE}|${NODE_NAME}|${BIND_IP}|${AGENT_PORT}"
|
||||||
|
|
||||||
|
# 执行主动推送
|
||||||
PUSH_RESULT=$(curl -s -X POST "${TG_API_URL}" \
|
PUSH_RESULT=$(curl -s -X POST "${TG_API_URL}" \
|
||||||
-d "chat_id=${CHAT_ID}" \
|
-d "chat_id=${CHAT_ID}" \
|
||||||
-d "parse_mode=Markdown" \
|
-d "parse_mode=Markdown" \
|
||||||
-d "text=✨ *IP-Sentinel 部署成功!*
|
-d "text=✨ *IP-Sentinel 部署成功!*
|
||||||
📍 区域:${REGION_NAME}
|
📍 区域:${REGION_NAME}
|
||||||
🌐 IP:${SAFE_PUBLIC_IP}
|
🌐 IP:${BIND_IP}
|
||||||
🔌 端口:${AGENT_PORT}
|
🔌 端口:${AGENT_PORT}
|
||||||
|
|
||||||
🔑 *请点击下方指令复制并回复给机器人:*
|
🔑 *请点击下方指令复制并回复给机器人:*
|
||||||
@@ -865,8 +500,8 @@ if [[ -n "$TG_TOKEN" ]]; then
|
|||||||
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active firewalld | grep -qw active; then
|
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
|
||||||
# 智能双栈雷达:根据对外公网 IP 属性,动态下发对应的防火墙放行指令
|
# 智能双栈雷达:根据绑定的 IP 属性,动态下发对应的防火墙放行指令
|
||||||
if [[ "$SAFE_PUBLIC_IP" == *":"* ]]; then
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
FW_MSG="ip6tables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
|
FW_MSG="ip6tables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
|
||||||
else
|
else
|
||||||
FW_MSG="iptables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
|
FW_MSG="iptables -I INPUT -p tcp --dport $AGENT_PORT -j ACCEPT"
|
||||||
@@ -891,5 +526,5 @@ if [ -n "$AGENT_COUNT" ] && [[ "$AGENT_COUNT" =~ ^[0-9]+$ ]]; then
|
|||||||
echo -e "\033[32m✅ 感谢您成为全球第 ${AGENT_COUNT} 名 IP-Sentinel 哨兵!\033[0m"
|
echo -e "\033[32m✅ 感谢您成为全球第 ${AGENT_COUNT} 名 IP-Sentinel 哨兵!\033[0m"
|
||||||
else
|
else
|
||||||
echo -e "\033[32m✅ 感谢您加入 IP-Sentinel 哨兵阵列!\033[0m"
|
echo -e "\033[32m✅ 感谢您加入 IP-Sentinel 哨兵阵列!\033[0m"
|
||||||
fi
|
fi
|
||||||
echo -e "\n"
|
echo -e "\n"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: mod_google.sh (Google 业务逻辑模块 - 动态锚点版)
|
# 脚本名称: mod_google.sh (Google 业务逻辑模块)
|
||||||
# 核心功能: 执行坐标微抖动、模拟真实阅读时长、会话行为拉伸
|
# 核心功能: 执行坐标微抖动、模拟真实阅读时长、会话行为拉伸
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -16,15 +16,11 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback (v3.4.0 引入版本探针)
|
# 容错机制:如果父进程没有传递 log 函数,则本地定义一个作为 fallback
|
||||||
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
|
||||||
|
|
||||||
@@ -52,8 +48,8 @@ get_random_coord() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- [环境初始化] ---
|
# --- [环境初始化] ---
|
||||||
# [v3.3.1修改] 优先读取对外公网面孔作为哈希种子,兼容 NAT 机的空 BIND_IP
|
# [v3.0.2修复] 直接读取系统已锁定的锚点 IP,彻底杜绝“获取IP失败”及隧道偏移
|
||||||
CURRENT_IP="${PUBLIC_IP:-${BIND_IP:-Unknown}}"
|
CURRENT_IP="${BIND_IP:-Unknown}"
|
||||||
|
|
||||||
# -----------------------------------------------------------
|
# -----------------------------------------------------------
|
||||||
# [V3.1.5] 哈希锚定法 (Hash-Seeded Persona)
|
# [V3.1.5] 哈希锚定法 (Hash-Seeded Persona)
|
||||||
@@ -97,21 +93,14 @@ CURL_BIND_OPT=""
|
|||||||
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
||||||
|
|
||||||
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
# [v3.6.3 容错层补丁] 探测物理网卡/虚拟 IP 存活状态
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
# 智能探测:带冒号为 V6,带点号为 V4
|
||||||
if ! ip addr show 2>/dev/null | grep -qw "$RAW_BIND_IP"; then
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
log "$MODULE_NAME" "WARN " "检测到配置的出口 IP ($RAW_BIND_IP) 已丢失,自动降级为系统默认路由出网!"
|
DYNAMIC_IP_PREF="-6"
|
||||||
CURL_BIND_OPT=""
|
log "$MODULE_NAME" "INFO " "底层路由锁定: 绑定 IPv6 出口及协议 ($BIND_IP)"
|
||||||
else
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
CURL_BIND_OPT="--interface $BIND_IP"
|
DYNAMIC_IP_PREF="-4"
|
||||||
# 智能探测:带冒号为 V6,带点号为 V4
|
log "$MODULE_NAME" "INFO " "底层路由锁定: 绑定 IPv4 出口及协议 ($BIND_IP)"
|
||||||
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
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: mod_trust.sh (IP 信用净化模块 - 动态锚点版)
|
# 脚本名称: mod_trust.sh (IP 信用净化模块 V3.1.4 拓扑自适应版)
|
||||||
# 核心功能: 动态扫描本地 LBS 冷数据,提取权威白名单,执行流量净化
|
# 核心功能: 动态扫描本地 LBS 冷数据,提取权威白名单,执行流量净化
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ UA_FILE="${INSTALL_DIR}/data/user_agents.txt"
|
|||||||
# 你的 GitHub 仓库 Raw 数据直链前缀
|
# 你的 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://raw.githubusercontent.com/hotyue/IP-Sentinel/v3.6.2-rc"
|
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
|
||||||
|
|
||||||
# 1. 基础环境校验
|
# 1. 基础环境校验
|
||||||
[ ! -f "$CONFIG_FILE" ] && exit 1
|
[ ! -f "$CONFIG_FILE" ] && exit 1
|
||||||
@@ -41,16 +41,12 @@ if [ ${#TRUST_URLS[@]} -eq 0 ]; then
|
|||||||
TRUST_URLS=("https://en.wikipedia.org/wiki/Special:Random" "https://www.apple.com/" "https://www.microsoft.com/")
|
TRUST_URLS=("https://en.wikipedia.org/wiki/Special:Random" "https://www.apple.com/" "https://www.microsoft.com/")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. 日志规范化 (v3.4.0 引入版本探针)
|
# 3. 日志规范化
|
||||||
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")
|
||||||
# [v3.4.0 核心] 提取当前配置中的版本锚点
|
echo "[$TIME] [$TYPE] [Trust ] [$REGION] $MSG" | tee -a "$LOG_FILE"
|
||||||
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. 锁定单次会话指纹
|
||||||
@@ -63,8 +59,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
|
||||||
# [v3.3.1修改] 优先使用固化的公网 IP 作为哈希种子,防止 NAT 节点指纹同质化
|
# 以本地锁定的公网 IP (BIND_IP) 为种子计算 CRC32 哈希值
|
||||||
SEED=$(echo -n "${PUBLIC_IP:-${BIND_IP:-127.0.0.1}}" | cksum | awk '{print $1}')
|
SEED=$(echo -n "${BIND_IP:-127.0.0.1}" | cksum | awk '{print $1}')
|
||||||
|
|
||||||
# 利用确定的种子,在全球 4000 的库中,计算出本机的 3 个绝对专属坐标
|
# 利用确定的种子,在全球 4000 的库中,计算出本机的 3 个绝对专属坐标
|
||||||
IDX1=$(( SEED % TOTAL_UA ))
|
IDX1=$(( SEED % TOTAL_UA ))
|
||||||
@@ -99,21 +95,14 @@ CURL_BIND_OPT=""
|
|||||||
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}" # 默认提取用户配置
|
||||||
|
|
||||||
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
# [v3.6.3 容错层补丁] 探测物理网卡/虚拟 IP 存活状态
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
# 智能探测:带冒号为 V6,带点号为 V4
|
||||||
if ! ip addr show 2>/dev/null | grep -qw "$RAW_BIND_IP"; then
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
log_msg "WARN " "检测到配置的出口 IP ($RAW_BIND_IP) 已丢失,自动降级为系统默认路由出网!"
|
DYNAMIC_IP_PREF="-6"
|
||||||
CURL_BIND_OPT=""
|
log_msg "INFO " "底层路由锁定: 绑定 IPv6 出口及协议 ($BIND_IP)"
|
||||||
else
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
CURL_BIND_OPT="--interface $BIND_IP"
|
DYNAMIC_IP_PREF="-4"
|
||||||
# 智能探测:带冒号为 V6,带点号为 V4
|
log_msg "INFO " "底层路由锁定: 绑定 IPv4 出口及协议 ($BIND_IP)"
|
||||||
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
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 - 动态锚点版)
|
# 脚本名称: runner.sh (IP-Sentinel 主控调度引擎 V2.0 智能分配版)
|
||||||
# 核心功能: 防并发延迟启动、功能开关(Feature Flag)自适应、多模块概率轮盘调度
|
# 核心功能: 防并发延迟启动、功能开关(Feature Flag)自适应、多模块概率轮盘调度
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -15,26 +15,14 @@ if [ ! -f "$CONFIG_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
# ================== [新增: 文件排他锁,防止并发重入引发内存雪崩] ==================
|
# 2. 全局日志写入函数 (导出给子进程共享使用)
|
||||||
exec 200>"/tmp/ip_sentinel_runner.lock"
|
|
||||||
if ! flock -n 200; then
|
|
||||||
echo "[$(date)] ⚠️ 上一轮巡逻任务尚未结束,本次触发自动取消。" >> "$LOG_FILE"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
# ==================================================================================
|
|
||||||
|
|
||||||
# 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,11 +16,6 @@ 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")
|
||||||
@@ -49,20 +44,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 "🚀 **[${NODE_NAME}]** 正在后台触发 IP 养护任务 (v${LOCAL_VER})..."
|
send_msg "🚀 **[指令下达]** 正在后台立即触发 IP 养护任务..."
|
||||||
# 使用 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 "📄 **[${NODE_NAME}] 实时日志 (v${LOCAL_VER}):**%0A\`\`\`log%0A${LOG_DATA}%0A\`\`\`"
|
send_msg "📄 **[最近 15 行系统日志]**%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📍 节点: \`${NODE_NAME}\`%0A🔖 版本: \`v${LOCAL_VER}\`%0A%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
|
HELP_MSG="🛡️ **IP-Sentinel 控制台**%0A/run - 立刻执行一次养护%0A/log - 抓取最新运行日志%0A/report - 手动生成统计简报"
|
||||||
send_msg "$HELP_MSG"
|
send_msg "$HELP_MSG"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: tg_report.sh (Telegram 每日战报模块 - 动态锚点版)
|
# 脚本名称: tg_report.sh (Telegram 每日战报模块 V6.0 动态拼装版)
|
||||||
# 核心功能: 适配 Feature Flag 架构,按需展示独立统计数据,OTA 更新预警
|
# 核心功能: 适配 Feature Flag 架构,按需展示 Google/Trust 独立统计数据
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
@@ -19,36 +19,25 @@ if [ -z "$TG_TOKEN" ] || [ -z "$CHAT_ID" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. 节点元数据抓取 (v3.2.2 协议自适应与多级容灾版)
|
# 2. 节点元数据抓取 (v3.2.2 协议自适应与多级容灾版)
|
||||||
# [v3.5.2 核心: 引入双轨身份架构]
|
NODE_NAME=$(hostname | cut -c 1-15)
|
||||||
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: 底层路由锁定与协议自适应] ---
|
||||||
CURL_BIND_OPT=""
|
CURL_BIND_OPT=""
|
||||||
DYNAMIC_IP_PREF="-${IP_PREF:-4}"
|
DYNAMIC_IP_PREF="-${IP_PREF:-4}"
|
||||||
|
|
||||||
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
if [[ -n "$BIND_IP" && "$BIND_IP" =~ ^[0-9a-fA-F:\.]+$ ]]; then
|
||||||
# [v3.6.3 容错层补丁] 探测物理网卡/虚拟 IP 存活状态
|
CURL_BIND_OPT="--interface $BIND_IP"
|
||||||
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
if [[ "$BIND_IP" == *":"* ]]; then
|
||||||
if ! ip addr show 2>/dev/null | grep -qw "$RAW_BIND_IP"; then
|
DYNAMIC_IP_PREF="-6"
|
||||||
CURL_BIND_OPT=""
|
elif [[ "$BIND_IP" == *"."* ]]; then
|
||||||
else
|
DYNAMIC_IP_PREF="-4"
|
||||||
CURL_BIND_OPT="--interface $BIND_IP"
|
|
||||||
if [[ "$BIND_IP" == *":"* ]]; then
|
|
||||||
DYNAMIC_IP_PREF="-6"
|
|
||||||
elif [[ "$BIND_IP" == *"."* ]]; then
|
|
||||||
DYNAMIC_IP_PREF="-4"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 多节点容灾探测出口 IP (注入协议自适应)
|
# 多节点容灾探测出口 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:]' )
|
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)
|
# 强制兜底:如果所有外部 API 都挂了,直接使用本地强行锁定的 BIND_IP
|
||||||
[ -z "$CURRENT_IP" ] && CURRENT_IP="${PUBLIC_IP:-$BIND_IP}"
|
[ -z "$CURRENT_IP" ] && CURRENT_IP="$BIND_IP"
|
||||||
|
|
||||||
# 为可能获取到的 IPv6 自动添加方括号护甲
|
# 为可能获取到的 IPv6 自动添加方括号护甲
|
||||||
[[ "$CURRENT_IP" == *":"* ]] && [[ "$CURRENT_IP" != *"["* ]] && CURRENT_IP="[${CURRENT_IP}]"
|
[[ "$CURRENT_IP" == *":"* ]] && [[ "$CURRENT_IP" != *"["* ]] && CURRENT_IP="[${CURRENT_IP}]"
|
||||||
@@ -92,18 +81,17 @@ case "$REGION_CODE" in
|
|||||||
"SG") FLAG="🇸🇬" ;;
|
"SG") FLAG="🇸🇬" ;;
|
||||||
"HK") FLAG="🇭🇰" ;;
|
"HK") FLAG="🇭🇰" ;;
|
||||||
"GB"|"UK") FLAG="🇬🇧" ;;
|
"GB"|"UK") FLAG="🇬🇧" ;;
|
||||||
"AU") FLAG="🇦🇺" ;;
|
|
||||||
*) FLAG="🌐" ;;
|
*) FLAG="🌐" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 3. 截取过去 24 小时的日志 (每天48次轮询,保留最新 1000 行足以覆盖单日战报)
|
# 3. 截取过去 24 小时的日志
|
||||||
LOG_CONTENT=$(tail -n 1000 "$LOG_FILE" 2>/dev/null)
|
LOG_CONTENT=$(find "$LOG_FILE" -mtime -1 -exec cat {} \; 2>/dev/null)
|
||||||
|
|
||||||
if [ -z "$LOG_CONTENT" ]; then
|
if [ -z "$LOG_CONTENT" ]; then
|
||||||
read -r -d '' MSG <<EOT
|
read -r -d '' MSG <<EOT
|
||||||
🛑 **[IP-Sentinel] 告警:节点异常**
|
🛑 **[IP-Sentinel] 告警:节点异常**
|
||||||
----------------------------
|
----------------------------
|
||||||
📍 **节点名称**: \`${NODE_ALIAS}\`
|
📍 **节点名称**: \`${NODE_NAME}\`
|
||||||
⚠️ **警告**: 过去 24 小时无运行日志!
|
⚠️ **警告**: 过去 24 小时无运行日志!
|
||||||
🛠️ **建议**: 节点可能刚部署完毕,请在面板手动执行一次养护动作。
|
🛠️ **建议**: 节点可能刚部署完毕,请在面板手动执行一次养护动作。
|
||||||
EOT
|
EOT
|
||||||
@@ -121,7 +109,7 @@ else
|
|||||||
# 开始组装战报头部
|
# 开始组装战报头部
|
||||||
MSG="📊 **IP-Sentinel 每日简报 (${FLAG} ${REGION_NAME})**
|
MSG="📊 **IP-Sentinel 每日简报 (${FLAG} ${REGION_NAME})**
|
||||||
----------------------------
|
----------------------------
|
||||||
📍 **节点名称**: \`${NODE_ALIAS}\`
|
📍 **节点名称**: \`${NODE_NAME}\`
|
||||||
📡 **出口 IP**: \`${CURRENT_IP}\`
|
📡 **出口 IP**: \`${CURRENT_IP}\`
|
||||||
🛡️ **IP 属性**: ${IP_TYPE}"
|
🛡️ **IP 属性**: ${IP_TYPE}"
|
||||||
|
|
||||||
@@ -165,39 +153,10 @@ else
|
|||||||
|
|
||||||
🕒 **最近执行快照 [${LAST_MOD:-"System"}]:**
|
🕒 **最近执行快照 [${LAST_MOD:-"System"}]:**
|
||||||
时间: ${LAST_TIME:-"暂无数据"}
|
时间: ${LAST_TIME:-"暂无数据"}
|
||||||
结论: ${LAST_SCORE:-"暂无数据"}"
|
结论: ${LAST_SCORE:-"暂无数据"}
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 5. [核心: OTA 云端版本探针与告警模块]
|
|
||||||
# ==========================================
|
|
||||||
# 从配置文件提取当前本地版本,若无则默认为未知
|
|
||||||
LOCAL_VER="${AGENT_VERSION:-未知}"
|
|
||||||
|
|
||||||
# 极轻量级探针: 抓取 GitHub 云端的 version.txt (超时 3 秒,KV解析法)
|
|
||||||
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
|
||||||
REMOTE_VER=$(curl -s -m 3 "${REPO_RAW_URL}/version.txt" | grep "^AGENT_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# 构建底部引擎状态块
|
|
||||||
MSG="$MSG
|
|
||||||
----------------------------
|
----------------------------
|
||||||
🛡️ **系统引擎状态**
|
💡 哨兵正在后台默默守护您的资产。"
|
||||||
当前运行版本: \`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
|
fi
|
||||||
|
|
||||||
# 5. 调用 API 推送 (接入安全网关)
|
# 5. 调用 API 推送 (接入安全网关)
|
||||||
|
|||||||
@@ -1,57 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 - 动态锚点版)
|
# ==========================================================
|
||||||
|
# 脚本名称: uninstall.sh (IP-Sentinel 一键卸载脚本 V3.1.4 焦土版)
|
||||||
# 核心功能: 无痕清理守护进程、定时任务、运行目录及临时缓存
|
# 核心功能: 无痕清理守护进程、定时任务、运行目录及临时缓存
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 🛑 核心权限防线: 检查是否以 root 权限运行
|
|
||||||
# ==========================================================
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
echo -e "\033[31m❌ 权限被拒绝: 卸载 IP-Sentinel 需要最高系统权限。\033[0m"
|
|
||||||
echo -e "💡 请切换到 root 用户 (执行 su root 或 sudo -i) 后重新运行指令。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
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. 停止并删除 Systemd 服务 (适配新架构)
|
# 1. 停止运行中的守护进程与主控模块 (涵盖所有历史版本进程)
|
||||||
echo "[1/4] 正在停止并删除 Systemd 服务..."
|
echo "[1/3] 正在终止后台守护进程与所有养护任务..."
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
echo "💡 检测到 Systemd 环境,正在抹除 Systemd 服务单元..."
|
|
||||||
systemctl disable --now ip-sentinel-runner.service ip-sentinel-runner.timer \
|
|
||||||
ip-sentinel-updater.service ip-sentinel-updater.timer \
|
|
||||||
ip-sentinel-report.service ip-sentinel-report.timer \
|
|
||||||
ip-sentinel-agent-daemon.service >/dev/null 2>&1
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-runner.service
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-runner.timer
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-updater.service
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-updater.timer
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-report.service
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-report.timer
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-agent-daemon.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl reset-failed
|
|
||||||
else
|
|
||||||
echo "💡 未检测到 Systemd,跳过此步骤..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. 停止运行中的守护进程与主控模块 (兜底清理老版进程)
|
# 使用 pkill 替代传统的 pgrep | xargs,指令更短、容错率更高
|
||||||
echo "[2/4] 正在终止后台守护进程与所有养护任务..."
|
|
||||||
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
|
||||||
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
|
||||||
@@ -59,16 +24,16 @@ pkill -9 -f "tg_report.sh" >/dev/null 2>&1
|
|||||||
pkill -9 -f "mod_google.sh" >/dev/null 2>&1
|
pkill -9 -f "mod_google.sh" >/dev/null 2>&1
|
||||||
pkill -9 -f "mod_trust.sh" >/dev/null 2>&1
|
pkill -9 -f "mod_trust.sh" >/dev/null 2>&1
|
||||||
|
|
||||||
# 3. 清除系统定时任务 (Cron)
|
# 2. 清除系统定时任务 (Cron)
|
||||||
echo "[3/4] 正在清理系统定时任务 (Cron)..."
|
echo "[2/3] 正在清理系统定时任务 (Cron)..."
|
||||||
if crontab -l >/dev/null 2>&1; then
|
if crontab -l >/dev/null 2>&1; then
|
||||||
crontab -l | grep -v "ip_sentinel" > /tmp/cron_backup
|
crontab -l | grep -v "ip_sentinel" > /tmp/cron_backup
|
||||||
crontab /tmp/cron_backup
|
crontab /tmp/cron_backup
|
||||||
rm -f /tmp/cron_backup
|
rm -f /tmp/cron_backup
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. 删除所有文件、日志与临时缓存
|
# 3. 删除所有文件、日志与临时缓存
|
||||||
echo "[4/4] 正在抹除核心程序、配置文件与系统痕迹..."
|
echo "[3/3] 正在抹除核心程序、配置文件与系统痕迹..."
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
rm -rf "$INSTALL_DIR"
|
rm -rf "$INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: updater.sh (IP-Sentinel 养料注入与分频调度中枢 - 动态锚点版)
|
# 脚本名称: updater.sh (IP-Sentinel V3.3.0 养料注入与分频调度中枢)
|
||||||
# 核心功能: 静默更新热数据/LBS、指纹库错峰调度、强制出站死锁、版本无缝继承
|
# 核心功能: 静默更新热搜词/LBS、指纹库错峰调度、强制出站死锁
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
INSTALL_DIR="/opt/ip_sentinel"
|
INSTALL_DIR="/opt/ip_sentinel"
|
||||||
@@ -11,8 +11,6 @@ UA_TIME_FILE="${INSTALL_DIR}/core/.ua_last_update"
|
|||||||
|
|
||||||
# GitHub 仓库 Raw 数据直链前缀
|
# 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://raw.githubusercontent.com/hotyue/IP-Sentinel/v3.6.2-rc"
|
|
||||||
|
|
||||||
# 1. 加载本地冷数据配置
|
# 1. 加载本地冷数据配置
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||||||
@@ -20,14 +18,10 @@ if [ ! -f "$CONFIG_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
# 2. 全局日志写入函数 (v3.4.0 引入版本探针)
|
# 2. 全局日志写入函数
|
||||||
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 热数据更新 =========="
|
||||||
@@ -42,12 +36,7 @@ CURL_CMD="curl -${IP_PREF:-4} -sL"
|
|||||||
if [ -n "$BIND_IP" ]; then
|
if [ -n "$BIND_IP" ]; then
|
||||||
# curl 的 --interface 参数不支持带方括号的 IPv6 地址,必须强行脱壳
|
# curl 的 --interface 参数不支持带方括号的 IPv6 地址,必须强行脱壳
|
||||||
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
RAW_BIND_IP=$(echo "$BIND_IP" | tr -d '[]')
|
||||||
# [v3.6.3 容错层补丁] 探测网卡存活状态,防止 IP 漂移导致永久断网
|
CURL_CMD="$CURL_CMD --interface $RAW_BIND_IP"
|
||||||
if ! ip addr show 2>/dev/null | grep -qw "$RAW_BIND_IP"; then
|
|
||||||
log "Updater" "WARN " "检测到绑定的出口 IP ($RAW_BIND_IP) 已丢失,自动退回默认路由!"
|
|
||||||
else
|
|
||||||
CURL_CMD="$CURL_CMD --interface $RAW_BIND_IP"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
crystal palace vs west ham
|
|
||||||
d4vd
|
|
||||||
danish malewar
|
|
||||||
mi vs gt
|
|
||||||
perth weather
|
|
||||||
elijah hollands carlton football club
|
|
||||||
fair work commission fuel
|
|
||||||
vanguard
|
|
||||||
sydney sweeney
|
|
||||||
cailee spaeny
|
|
||||||
psg vs lyon
|
|
||||||
sporting vs benfica
|
|
||||||
celtics vs 76ers
|
|
||||||
psl
|
|
||||||
bundesliga
|
|
||||||
pl
|
|
||||||
afc cup
|
|
||||||
mars rover
|
|
||||||
celtic fc
|
|
||||||
bayern vs vfb stuttgart
|
|
||||||
bom radar
|
|
||||||
sydney weather
|
|
||||||
melbourne weather
|
|
||||||
brisbane weather
|
|
||||||
adelaide weather
|
|
||||||
myGov login
|
|
||||||
news.com.au
|
|
||||||
abc news
|
|
||||||
nrl ladder
|
|
||||||
afl scores
|
|
||||||
afl fixture
|
|
||||||
matildas
|
|
||||||
sam kerr
|
|
||||||
bunnings opening hours
|
|
||||||
coles catalogue
|
|
||||||
woolies specials
|
|
||||||
qantas
|
|
||||||
virgin australia
|
|
||||||
jetstar flights
|
|
||||||
commbank
|
|
||||||
asx 200
|
|
||||||
sydney morning herald
|
|
||||||
the age
|
|
||||||
ticketek
|
|
||||||
ticketmaster
|
|
||||||
opal card top up
|
|
||||||
myki top up
|
|
||||||
translink timetable
|
|
||||||
transperth journey planner
|
|
||||||
adelaide metro
|
|
||||||
adelaide 500
|
|
||||||
adelaide crows
|
|
||||||
port adelaide
|
|
||||||
penrith panthers
|
|
||||||
brisbane broncos
|
|
||||||
collingwood fc
|
|
||||||
sydney swans
|
|
||||||
west coast eagles
|
|
||||||
fremantle dockers
|
|
||||||
canberra raiders
|
|
||||||
mcg events
|
|
||||||
optus stadium
|
|
||||||
the gabba
|
|
||||||
state of origin
|
|
||||||
australian open
|
|
||||||
melbourne cup
|
|
||||||
masterchef australia
|
|
||||||
mafs australia
|
|
||||||
petrol prices near me
|
|
||||||
australia post tracking
|
|
||||||
service nsw login
|
|
||||||
vicroads
|
|
||||||
queensland health
|
|
||||||
medicare
|
|
||||||
ato
|
|
||||||
jb hi-fi
|
|
||||||
kmart
|
|
||||||
amazon.com.au
|
|
||||||
vivid sydney
|
|
||||||
mona hobart
|
|
||||||
dark mofo
|
|
||||||
floriade canberra
|
|
||||||
rottnest island ferry
|
|
||||||
sydney airport arrivals
|
|
||||||
rba interest rate
|
|
||||||
nsw school holidays
|
|
||||||
qld school holidays
|
|
||||||
wa school holidays
|
|
||||||
m4 traffic updates
|
|
||||||
west gate tunnel updates
|
|
||||||
bruce highway traffic
|
|
||||||
taylor swift
|
|
||||||
netflix
|
|
||||||
gemini
|
|
||||||
chatgpt
|
|
||||||
margot robbie
|
|
||||||
ange postecoglou
|
|
||||||
oscar piastri
|
|
||||||
formula 1
|
|
||||||
supercars championship
|
|
||||||
ufc
|
|
||||||
optus sport
|
|
||||||
telstra outage
|
|
||||||
kayo sports
|
|
||||||
domain real estate
|
|
||||||
realestate.com.au
|
|
||||||
seek jobs
|
|
||||||
gumtree
|
|
||||||
westpac login
|
|
||||||
anz internet banking
|
|
||||||
nab login
|
|
||||||
aldi catalogue
|
|
||||||
big w
|
|
||||||
target australia
|
|
||||||
the good guys
|
|
||||||
officeworks
|
|
||||||
dan murphys
|
|
||||||
bws
|
|
||||||
chemist warehouse
|
|
||||||
priceline
|
|
||||||
uber eats
|
|
||||||
menulog
|
|
||||||
dominos
|
|
||||||
centrelink login
|
|
||||||
medicare online
|
|
||||||
passport renewal
|
|
||||||
aec enrolment
|
|
||||||
agl energy
|
|
||||||
origin energy
|
|
||||||
stan
|
|
||||||
binge
|
|
||||||
9now
|
|
||||||
7plus
|
|
||||||
abc iview
|
|
||||||
hoyts cinemas
|
|
||||||
event cinemas
|
|
||||||
big bash league
|
|
||||||
the ashes
|
|
||||||
socceroos
|
|
||||||
bathurst 1000
|
|
||||||
booking.com
|
|
||||||
airbnb
|
|
||||||
webjet
|
|
||||||
bali weather
|
|
||||||
smartraveller
|
|
||||||
lotto results
|
|
||||||
powerball results
|
|
||||||
oz lotto
|
|
||||||
public holidays 2026
|
|
||||||
mothers day 2026
|
|
||||||
carsales
|
|
||||||
redbook
|
|
||||||
nrma
|
|
||||||
racv
|
|
||||||
racq
|
|
||||||
petrol spy
|
|
||||||
optus login
|
|
||||||
vodafone
|
|
||||||
kogan
|
|
||||||
catch.com.au
|
|
||||||
mecca
|
|
||||||
sydney fish market
|
|
||||||
queen victoria market
|
|
||||||
taronga zoo
|
|
||||||
dreamworld
|
|
||||||
movie world
|
|
||||||
wotif
|
|
||||||
anytime fitness
|
|
||||||
time in london
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
will trent
|
|
||||||
ibm
|
|
||||||
lecce vs fiorentina
|
|
||||||
celeste rivas hernandez
|
|
||||||
grand theft auto vi
|
|
||||||
abhishek bachchan
|
|
||||||
national guard of the united states
|
|
||||||
triathlon
|
|
||||||
vincent trocheck
|
|
||||||
benyamin netanyahou
|
|
||||||
kings vs avalanche
|
|
||||||
crème solaire
|
|
||||||
trabzonspor vs istanbul başakşehir
|
|
||||||
indigenous rights
|
|
||||||
meghan, duchess of sussex
|
|
||||||
gwyneth paltrow
|
|
||||||
toronto gas prices
|
|
||||||
président
|
|
||||||
mets vs cubs
|
|
||||||
matt fitzpatrick
|
|
||||||
sénateurs – hurricanes
|
|
||||||
senators vs hurricanes
|
|
||||||
man utd
|
|
||||||
spurs
|
|
||||||
evan mobley
|
|
||||||
chelsea vs man united
|
|
||||||
atlético madrid vs real sociedad
|
|
||||||
roma vs atalanta
|
|
||||||
cf montreal
|
|
||||||
jakob poeltl
|
|
||||||
ukraine
|
|
||||||
contrôle routier québec
|
|
||||||
bachelorette 2026
|
|
||||||
lens – toulouse
|
|
||||||
arber xhekaj
|
|
||||||
leylah fernandez
|
|
||||||
anthropic
|
|
||||||
paige wwe
|
|
||||||
inter – cagliari
|
|
||||||
carrie ann inaba
|
|
||||||
syria
|
|
||||||
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,102 +1,3 @@
|
|||||||
heppenheim
|
|
||||||
ministerpräsident
|
|
||||||
robbie williams
|
|
||||||
brian littrell
|
|
||||||
saarbrücken hauptbahnhof
|
|
||||||
frauen-bundesliga
|
|
||||||
lemgo
|
|
||||||
kommissar rex
|
|
||||||
hemsbach
|
|
||||||
benjamin weber
|
|
||||||
sydney sweeney
|
|
||||||
martin schindler
|
|
||||||
robert kennedy
|
|
||||||
antoni kowalski
|
|
||||||
luna
|
|
||||||
paris-sg – lyon
|
|
||||||
arte live
|
|
||||||
dominik kohr
|
|
||||||
dumbledores geheimnisse
|
|
||||||
kampf der realitystars
|
|
||||||
kerner
|
|
||||||
tennessee
|
|
||||||
billy idol
|
|
||||||
kfc uerdingen
|
|
||||||
bastian pastewka
|
|
||||||
gnabry
|
|
||||||
lok leipzig heute live ticker
|
|
||||||
true lies wahre lügen
|
|
||||||
челси – манчестер юнайтед
|
|
||||||
nurburgring
|
|
||||||
fortnite server status
|
|
||||||
süperlig
|
|
||||||
ronaldinho
|
|
||||||
esther schweins let's dance
|
|
||||||
michael jackson film 2026
|
|
||||||
oliver pocher
|
|
||||||
die queen film
|
|
||||||
milano
|
|
||||||
straße von hormus
|
|
||||||
motsi mabuse
|
|
||||||
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
|
|
||||||
манчестер юнайтед – лидс
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
moreirense - estoril
|
|
||||||
downton abbey
|
|
||||||
koldo garcía izaguirre
|
|
||||||
jorge martín
|
|
||||||
mike james
|
|
||||||
carla leite
|
|
||||||
eclipse solar del 12 de agosto de 2026
|
|
||||||
crystal palace - west ham
|
|
||||||
luis merlo
|
|
||||||
luz valdenebro
|
|
||||||
porto - tondela
|
|
||||||
santos - fluminense
|
|
||||||
juventus
|
|
||||||
almería - málaga
|
|
||||||
punjab kings vs lucknow super giants standings
|
|
||||||
mönchengladbach – mainz
|
|
||||||
victor eloy
|
|
||||||
paris-sg – lyon
|
|
||||||
psg vs lyon
|
|
||||||
zamora - osasuna b
|
|
||||||
estrecho
|
|
||||||
nurburgring
|
|
||||||
la 1 directo
|
|
||||||
oyarzabal
|
|
||||||
enrique cerezo
|
|
||||||
fraude
|
|
||||||
tasa
|
|
||||||
la 1
|
|
||||||
tve directo
|
|
||||||
cuántas copas del rey tiene la real sociedad
|
|
||||||
eugenia martínez de irujo
|
|
||||||
ccoo
|
|
||||||
racing de santander
|
|
||||||
racing
|
|
||||||
baliza v16
|
|
||||||
st. pauli – köln
|
|
||||||
iphone 18
|
|
||||||
st. pauli - colonia
|
|
||||||
nico paz
|
|
||||||
lionel messi
|
|
||||||
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,102 +1,3 @@
|
|||||||
madonna age
|
|
||||||
margot haddad
|
|
||||||
ana riera
|
|
||||||
hinaupoko devèze
|
|
||||||
bruce toussaint
|
|
||||||
cheque energie
|
|
||||||
reid wiseman
|
|
||||||
mma
|
|
||||||
loto 20 avril 2026
|
|
||||||
from serie
|
|
||||||
toulouse
|
|
||||||
racing 92 – stade français
|
|
||||||
juventus - bologna
|
|
||||||
film une annee difficile
|
|
||||||
échouement
|
|
||||||
programme tv ce soir
|
|
||||||
porto – tondela
|
|
||||||
matthieu pigasse
|
|
||||||
santos – fluminense
|
|
||||||
gta 6
|
|
||||||
laetitia milot
|
|
||||||
loto 18 avril 2026
|
|
||||||
bercy
|
|
||||||
pierre lellouche
|
|
||||||
adele
|
|
||||||
adil rami
|
|
||||||
castres – toulouse
|
|
||||||
angel
|
|
||||||
stéphane bern
|
|
||||||
anne claire coudray
|
|
||||||
pmu résultat
|
|
||||||
laury thilleman et paul mirabel
|
|
||||||
quinté du jour
|
|
||||||
euromillions 17 avril 2026
|
|
||||||
alain bauer
|
|
||||||
uson
|
|
||||||
guillaume meurice
|
|
||||||
pmu
|
|
||||||
grenoble – oyonnax
|
|
||||||
bagarre
|
|
||||||
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,99 +1,3 @@
|
|||||||
水晶宮 對 西漢姆聯
|
|
||||||
吉達艾阿里
|
|
||||||
山口智子
|
|
||||||
百佳超級市場
|
|
||||||
利息
|
|
||||||
戴祖儀
|
|
||||||
陳若思
|
|
||||||
麥當勞
|
|
||||||
首岸
|
|
||||||
中国商飞c919
|
|
||||||
pl
|
|
||||||
bundesliga
|
|
||||||
hailey bieber
|
|
||||||
德甲
|
|
||||||
cherki
|
|
||||||
now
|
|
||||||
曼城
|
|
||||||
now tv
|
|
||||||
al nassr
|
|
||||||
洪金寶
|
|
||||||
曼聯
|
|
||||||
車路士
|
|
||||||
切爾西 對 曼聯
|
|
||||||
英超
|
|
||||||
何沛珈
|
|
||||||
熱刺
|
|
||||||
tottenham vs brighton
|
|
||||||
熱刺 對 布萊頓
|
|
||||||
epl
|
|
||||||
司機
|
|
||||||
补贴
|
|
||||||
華富邨
|
|
||||||
江美儀
|
|
||||||
零售
|
|
||||||
藍莓
|
|
||||||
商湯科技
|
|
||||||
周國豐
|
|
||||||
啟點
|
|
||||||
歐聯
|
|
||||||
神戶勝利船
|
|
||||||
潘宏彬
|
|
||||||
姚正菁
|
|
||||||
木乃伊
|
|
||||||
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,103 +1,7 @@
|
|||||||
町田ゼルビア
|
|
||||||
モンスターハンターシリーズ
|
|
||||||
クリスタル・パレス 対 ウェストハム
|
|
||||||
吉田正尚
|
|
||||||
旭琉會
|
|
||||||
神戸
|
|
||||||
kick
|
|
||||||
てんちむ
|
|
||||||
浜辺美波
|
|
||||||
デーブ ロバーツ
|
|
||||||
皇治
|
|
||||||
小雪
|
|
||||||
にじさんじデビュー
|
|
||||||
ちゃんよた
|
|
||||||
坂本 花織
|
|
||||||
松山 千春
|
|
||||||
dior
|
|
||||||
リーグアン
|
|
||||||
伊勢谷友介
|
|
||||||
bayern vs vfb stuttgart
|
|
||||||
chelsea vs man united
|
|
||||||
唐田えりか
|
|
||||||
102回目のプロポーズ
|
|
||||||
デゼルビ
|
|
||||||
アトレティコ 対 ソシエダ
|
|
||||||
チェルシー 対 マンu
|
|
||||||
スパーズ
|
|
||||||
清春
|
|
||||||
ディエゴ・ゴメス
|
|
||||||
ps5
|
|
||||||
ハイウェイ の 堕 天使 興行 収入
|
|
||||||
カブス 対 メッツ
|
|
||||||
dazn
|
|
||||||
サッスオーロ 対 コモ
|
|
||||||
杉咲花
|
|
||||||
町田 対 アル・イテハド
|
|
||||||
家計
|
|
||||||
週末旅の極意
|
|
||||||
北斗の拳
|
|
||||||
qvc
|
|
||||||
小芝風花
|
|
||||||
中井亜美
|
|
||||||
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
|
|
||||||
サンディスク 株価
|
|
||||||
らじるらじる
|
|
||||||
マクドナルド
|
|
||||||
ロシア
|
|
||||||
広島市
|
|
||||||
ゲイブル・スティーブソン
|
|
||||||
日本維新の会
|
|
||||||
新 日本 繊維
|
|
||||||
高見沢 俊彦
|
|
||||||
不登校
|
|
||||||
後期高齢者医療制度
|
|
||||||
バーミヤン
|
|
||||||
宮澤エマ
|
|
||||||
チケプラ
|
|
||||||
横綱
|
|
||||||
宮里美香
|
|
||||||
東京 天気 明日
|
東京 天気 明日
|
||||||
新宿 おすすめ 居酒屋
|
新宿 おすすめ 居酒屋
|
||||||
最新のニュース 速報
|
最新のニュース 速報
|
||||||
ゴールド 相場 チャート
|
ゴールド 相場 チャート
|
||||||
近くの静かなカフェ
|
近くの静かなカフェ
|
||||||
円安 影響 生活
|
地震 速報
|
||||||
|
円安 影響 生活
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
크리스털 팰리스 대 웨스트 햄
|
|
||||||
비상계엄
|
|
||||||
연기금
|
|
||||||
배틀그라운드
|
|
||||||
제이홉
|
|
||||||
두산로보틱스
|
|
||||||
부부
|
|
||||||
미노이 우원재
|
|
||||||
danish malewar
|
|
||||||
mi vs gt
|
|
||||||
양치 승
|
|
||||||
프리미어리그
|
|
||||||
pl
|
|
||||||
bundesliga
|
|
||||||
맨체스터 시티 fc
|
|
||||||
fotmob
|
|
||||||
윤아
|
|
||||||
김영인
|
|
||||||
манчестер сити – арсенал
|
|
||||||
케이뱅크
|
|
||||||
날씨
|
|
||||||
환율
|
|
||||||
삼성전자 주가
|
|
||||||
손흥민
|
|
||||||
토트넘 경기 일정
|
|
||||||
넷플릭스
|
|
||||||
국민은행
|
|
||||||
네이버웹툰
|
|
||||||
로또 당첨번호
|
|
||||||
쿠팡
|
|
||||||
KBO 리그
|
|
||||||
프로야구 순위
|
|
||||||
아이폰 15
|
|
||||||
챗gpt
|
|
||||||
인스타그램
|
|
||||||
유튜브 밴스드
|
|
||||||
이강인
|
|
||||||
김민재
|
|
||||||
LCK 일정
|
|
||||||
T1
|
|
||||||
페이커
|
|
||||||
무빙
|
|
||||||
카카오톡 PC버전
|
|
||||||
당근마켓
|
|
||||||
배달의민족
|
|
||||||
올리브영
|
|
||||||
메이플스토리
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
lecce - fiorentina
|
|
||||||
pogoda jutro
|
|
||||||
vierhouten
|
|
||||||
weather tomorrow
|
|
||||||
over mijn lijk 2026 overleden
|
|
||||||
crystal palace - west ham
|
|
||||||
f-16 fighting falcon
|
|
||||||
klagenfurt
|
|
||||||
italië
|
|
||||||
ruud gullit
|
|
||||||
perfil falso
|
|
||||||
psg - lyon
|
|
||||||
juventus - bologna
|
|
||||||
troy parrott
|
|
||||||
az - nec opstellingen
|
|
||||||
caroline tensen
|
|
||||||
asielbeleid
|
|
||||||
philippe sandler
|
|
||||||
keerbergen
|
|
||||||
peer koopmeiners
|
|
||||||
nederlands elftal
|
|
||||||
pogoń szczecin – lech poznań
|
|
||||||
stand premier league
|
|
||||||
bulgarije
|
|
||||||
chelsea - man utd
|
|
||||||
atlético madrid - real sociedad
|
|
||||||
xavi simons
|
|
||||||
ayase ueda
|
|
||||||
roma - atalanta
|
|
||||||
rtv noord
|
|
||||||
paraguay
|
|
||||||
sergio herman
|
|
||||||
fed
|
|
||||||
keuken kampioen
|
|
||||||
legia warszawa – zagłębie lubin
|
|
||||||
ripple
|
|
||||||
voorzitter fed
|
|
||||||
bahamas
|
|
||||||
overtreding
|
|
||||||
almere city
|
|
||||||
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,102 +1,3 @@
|
|||||||
crystal palace vs west ham
|
|
||||||
grok
|
|
||||||
gemini
|
|
||||||
claude ai
|
|
||||||
gpt
|
|
||||||
ai
|
|
||||||
is chatgpt down
|
|
||||||
strait of hormuz news
|
|
||||||
danish malewar
|
|
||||||
mi vs gt
|
|
||||||
psg vs lyon
|
|
||||||
sporting vs benfica
|
|
||||||
bayern munich
|
|
||||||
pl
|
|
||||||
bangkok weather
|
|
||||||
starhub
|
|
||||||
廖子妤
|
|
||||||
曼城 - 阿森纳
|
|
||||||
bayern vs vfb stuttgart
|
|
||||||
英超
|
|
||||||
chelsea vs man united
|
|
||||||
perfect crown
|
|
||||||
ayush mhatre
|
|
||||||
sarfaraz khan
|
|
||||||
napoli vs lazio
|
|
||||||
tottenham vs brighton
|
|
||||||
abhishek sharma
|
|
||||||
tinie tempah
|
|
||||||
wrexham vs stoke city
|
|
||||||
sassuolo vs como
|
|
||||||
the straits times
|
|
||||||
india women vs south africa women
|
|
||||||
beef season 2
|
|
||||||
loyang valley
|
|
||||||
world cup 2026
|
|
||||||
afc champions league
|
|
||||||
hormuz
|
|
||||||
malacca strait
|
|
||||||
kkr vs gt
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
洪敬堯
|
|
||||||
華邦電子
|
|
||||||
柔美的細胞小將 3
|
|
||||||
底特律老虎
|
|
||||||
三 商 美邦
|
|
||||||
川 普
|
|
||||||
英雄聯盟
|
|
||||||
聯電adr
|
|
||||||
rklb
|
|
||||||
amd stock
|
|
||||||
宋仲基
|
|
||||||
兵工廠
|
|
||||||
德甲
|
|
||||||
塞爾提克 對 76人
|
|
||||||
justin bieber 演唱會
|
|
||||||
拜仁慕尼黑
|
|
||||||
何潤東
|
|
||||||
廖子妤
|
|
||||||
英超
|
|
||||||
許凱
|
|
||||||
tottenham vs brighton
|
|
||||||
長野縣
|
|
||||||
交通安全
|
|
||||||
朋友收集夢想生活
|
|
||||||
麥克傑克森
|
|
||||||
王濛
|
|
||||||
騎士 對 暴龍
|
|
||||||
熱刺 對 布萊頓
|
|
||||||
iem rio 2026
|
|
||||||
garret anderson
|
|
||||||
墓乃伊
|
|
||||||
曹格
|
|
||||||
claude design
|
|
||||||
柯文哲
|
|
||||||
金剛
|
|
||||||
荷 姆 茲 海峽
|
|
||||||
東北 季風
|
|
||||||
斯圖加特公開賽
|
|
||||||
歐聯
|
|
||||||
菡生婦幼診所
|
|
||||||
台鐵訂票
|
|
||||||
飛機
|
|
||||||
東光路
|
|
||||||
货币
|
|
||||||
amd
|
|
||||||
航空母艦
|
|
||||||
axti
|
|
||||||
Yahoo奇摩
|
|
||||||
天氣
|
|
||||||
蝦皮購物
|
|
||||||
PChome
|
|
||||||
Momo購物網
|
|
||||||
Mobile01
|
|
||||||
Dcard
|
|
||||||
巴哈姆特
|
|
||||||
中時電子報
|
|
||||||
聯合新聞網
|
|
||||||
台灣高鐵
|
|
||||||
台鐵時刻表
|
|
||||||
中華電信
|
|
||||||
統一發票
|
|
||||||
勞動部
|
|
||||||
@@ -1,103 +1,3 @@
|
|||||||
oscar isaac
|
|
||||||
xrp ledger
|
|
||||||
believe me itv
|
|
||||||
pablo
|
|
||||||
is tane leaving home and away
|
|
||||||
julie andrews
|
|
||||||
danny boyle
|
|
||||||
nina eastenders
|
|
||||||
john stones
|
|
||||||
amazon vega os fire tv
|
|
||||||
porto vs tondela
|
|
||||||
santos vs fluminense
|
|
||||||
martin brundle lost f1 seat
|
|
||||||
keegan bradley
|
|
||||||
david attenborough
|
|
||||||
antoni kowalski
|
|
||||||
kezia dugdale
|
|
||||||
car
|
|
||||||
beef netflix
|
|
||||||
juventus vs bologna
|
|
||||||
losc vs nice
|
|
||||||
david szalay
|
|
||||||
the killer
|
|
||||||
joe cole
|
|
||||||
lille fc
|
|
||||||
simon cowell
|
|
||||||
pl
|
|
||||||
frank lampard everton
|
|
||||||
nottingham forest fixtures
|
|
||||||
everton manager
|
|
||||||
dragons vs bulls
|
|
||||||
suede
|
|
||||||
lahore
|
|
||||||
wrestlemania 2026
|
|
||||||
giants vs rhinos
|
|
||||||
glenrothan
|
|
||||||
york knights vs leopards
|
|
||||||
tim sherwood
|
|
||||||
redditch
|
|
||||||
ccfc
|
|
||||||
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,100 +1,3 @@
|
|||||||
arc raiders
|
|
||||||
schd etf dividend yield
|
|
||||||
deportación
|
|
||||||
lecce - fiorentina
|
|
||||||
valley forge high school
|
|
||||||
betty yee
|
|
||||||
seth trimble
|
|
||||||
bill belichick
|
|
||||||
winter storm
|
|
||||||
jim parsons
|
|
||||||
kings vs avalanche
|
|
||||||
suns vs thunder
|
|
||||||
wolf
|
|
||||||
santos - fluminense
|
|
||||||
mets - cubs
|
|
||||||
alexander manninger
|
|
||||||
santos vs fluminense
|
|
||||||
disclosure day
|
|
||||||
tobias myers
|
|
||||||
vladimir putin
|
|
||||||
knicks game
|
|
||||||
ben rice
|
|
||||||
prem
|
|
||||||
timberwolves vs nuggets
|
|
||||||
cody bellinger
|
|
||||||
nik khamenia
|
|
||||||
real sociedad
|
|
||||||
nurburgring crash
|
|
||||||
atlético madrid - real sociedad
|
|
||||||
ruke orhorhoro
|
|
||||||
radar
|
|
||||||
the weather channel
|
|
||||||
kttc
|
|
||||||
luke gulbranson
|
|
||||||
kttc weather
|
|
||||||
comcast data breach settlement
|
|
||||||
tornado watch
|
|
||||||
moisés ballesteros
|
|
||||||
mets game today
|
|
||||||
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
|
||||||
@@ -104,6 +7,3 @@ 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
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
lecce vs fiorentina
|
|
||||||
fiorentina
|
|
||||||
crystal palace đấu với west ham
|
|
||||||
afc champions league
|
|
||||||
eduardo camavinga
|
|
||||||
vissel kobe
|
|
||||||
gemi
|
|
||||||
idp
|
|
||||||
bảo hiểm y tế
|
|
||||||
crystal palace vs west ham
|
|
||||||
sporting lisbon
|
|
||||||
psg vs lyon
|
|
||||||
ca sĩ
|
|
||||||
juventus đấu với bologna
|
|
||||||
gladbach đấu với mainz
|
|
||||||
bayern munich
|
|
||||||
twitch
|
|
||||||
psg đấu với lyon
|
|
||||||
juventus
|
|
||||||
ligue 1
|
|
||||||
trận đấu ngoại hạng anh
|
|
||||||
chelsea đấu với man utd
|
|
||||||
atlético madrid đấu với real sociedad
|
|
||||||
roma đấu với atalanta
|
|
||||||
epl
|
|
||||||
iem rio 2026
|
|
||||||
tot
|
|
||||||
tập đoàn gelex
|
|
||||||
napoli
|
|
||||||
đường ray
|
|
||||||
inter
|
|
||||||
inter đấu với cagliari
|
|
||||||
sassuolo vs como
|
|
||||||
david alaba
|
|
||||||
claude design
|
|
||||||
fenerbahçe đấu với rizespor
|
|
||||||
como
|
|
||||||
como vs
|
|
||||||
thẻ đỏ
|
|
||||||
al ahli
|
|
||||||
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
|
|
||||||
314
data/map.json
314
data/map.json
@@ -1,239 +1,105 @@
|
|||||||
{
|
{
|
||||||
"version": "3.5.2",
|
"version": "3.1.0",
|
||||||
"updated_at": "2026-04-19",
|
"updated_at": "2026-04-11",
|
||||||
"continents": [
|
"countries": [
|
||||||
{
|
{
|
||||||
"id": "ASIA",
|
"id": "US",
|
||||||
"name": "亚太战区 (Asia-Pacific)",
|
"name": "United States (美国)",
|
||||||
"countries": [
|
"keyword_file": "kw_US.txt",
|
||||||
{
|
"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": "KR",
|
|
||||||
"name": "South Korea (韩国)",
|
|
||||||
"keyword_file": "kw_KR.txt",
|
|
||||||
"states": [
|
|
||||||
{ "id": "Default", "name": "Default State", "cities": [ { "id": "Seoul", "name": "Seoul (首尔)" } ] }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "Canada (加拿大)",
|
"name": "California (加州)",
|
||||||
"keyword_file": "kw_CA.txt",
|
"cities": [
|
||||||
"states": [
|
{ "id": "Los_Angeles", "name": "Los Angeles (洛杉矶)" },
|
||||||
{
|
{ "id": "San_Jose", "name": "San Jose (圣何塞)" }
|
||||||
"id": "Default",
|
|
||||||
"name": "Default State",
|
|
||||||
"cities": [
|
|
||||||
{ "id": "Toronto", "name": "Toronto (多伦多)" },
|
|
||||||
{ "id": "Montreal", "name": "Montreal (蒙特利尔)" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "OCEANIA",
|
"id": "JP",
|
||||||
"name": "大洋洲战区 (Oceania)",
|
"name": "Japan (日本)",
|
||||||
"countries": [
|
"keyword_file": "kw_JP.txt",
|
||||||
|
"states": [
|
||||||
{
|
{
|
||||||
"id": "AU",
|
"id": "Default",
|
||||||
"name": "Australia (澳大利亚)",
|
"name": "Default State",
|
||||||
"keyword_file": "kw_AU.txt",
|
"cities": [
|
||||||
"states": [
|
{ "id": "Tokyo", "name": "Tokyo (东京)" }
|
||||||
{
|
]
|
||||||
"id": "NSW",
|
}
|
||||||
"name": "New South Wales (新南威尔士州)",
|
]
|
||||||
"cities": [
|
},
|
||||||
{ "id": "Sydney", "name": "Sydney (悉尼)" }
|
{
|
||||||
]
|
"id": "UK",
|
||||||
},
|
"name": "United Kingdom (英国)",
|
||||||
{
|
"keyword_file": "kw_UK.txt",
|
||||||
"id": "VIC",
|
"states": [
|
||||||
"name": "Victoria (维多利亚州)",
|
{
|
||||||
"cities": [
|
"id": "Default",
|
||||||
{ "id": "Melbourne", "name": "Melbourne (墨尔本)" }
|
"name": "Default State",
|
||||||
]
|
"cities": [
|
||||||
},
|
{ "id": "London", "name": "London (伦敦)" }
|
||||||
{
|
]
|
||||||
"id": "QLD",
|
}
|
||||||
"name": "Queensland (昆士兰州)",
|
]
|
||||||
"cities": [
|
},
|
||||||
{ "id": "Brisbane", "name": "Brisbane (布里斯班)" }
|
{
|
||||||
]
|
"id": "DE",
|
||||||
},
|
"name": "Germany (德国)",
|
||||||
{
|
"keyword_file": "kw_DE.txt",
|
||||||
"id": "WA",
|
"states": [
|
||||||
"name": "Western Australia (西澳大利亚州)",
|
{
|
||||||
"cities": [
|
"id": "Default",
|
||||||
{ "id": "Perth", "name": "Perth (珀斯)" }
|
"name": "Default State",
|
||||||
]
|
"cities": [
|
||||||
},
|
{ "id": "Frankfurt", "name": "Frankfurt (法兰克福)" }
|
||||||
{
|
]
|
||||||
"id": "SA",
|
}
|
||||||
"name": "South Australia (南澳大利亚州)",
|
]
|
||||||
"cities": [
|
},
|
||||||
{ "id": "Adelaide", "name": "Adelaide (阿德莱德)" }
|
{
|
||||||
]
|
"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 (香港)" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Australia - Sydney",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": -33.8697,
|
|
||||||
"base_lon": 151.2085,
|
|
||||||
"lang_params": "hl=en-AU&gl=AU",
|
|
||||||
"valid_url_suffix": "com.au"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.smh.com.au/",
|
|
||||||
"https://www.dailytelegraph.com.au/",
|
|
||||||
"https://www.service.nsw.gov.au/",
|
|
||||||
"https://transportnsw.info/",
|
|
||||||
"https://www.bom.gov.au/",
|
|
||||||
"https://www.woolworths.com.au/",
|
|
||||||
"https://www.coles.com.au/",
|
|
||||||
"https://www.bunnings.com.au/",
|
|
||||||
"https://www.abc.net.au/",
|
|
||||||
"https://www.news.com.au/",
|
|
||||||
"https://www.nine.com.au/",
|
|
||||||
"https://my.gov.au/",
|
|
||||||
"https://www.ato.gov.au/",
|
|
||||||
"https://www.commbank.com.au/",
|
|
||||||
"https://www.westpac.com.au/",
|
|
||||||
"https://www.realestate.com.au/",
|
|
||||||
"https://www.domain.com.au/",
|
|
||||||
"https://www.seek.com.au/",
|
|
||||||
"https://www.nrl.com/",
|
|
||||||
"https://premier.ticketek.com.au/",
|
|
||||||
"https://www.amazon.com.au/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Australia - Brisbane",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": -27.4697,
|
|
||||||
"base_lon": 153.0252,
|
|
||||||
"lang_params": "hl=en-AU&gl=AU",
|
|
||||||
"valid_url_suffix": "com.au"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.couriermail.com.au/",
|
|
||||||
"https://www.brisbanetimes.com.au/",
|
|
||||||
"https://www.qld.gov.au/",
|
|
||||||
"https://translink.com.au/",
|
|
||||||
"https://www.health.qld.gov.au/",
|
|
||||||
"https://www.bom.gov.au/",
|
|
||||||
"https://www.bunnings.com.au/",
|
|
||||||
"https://www.woolworths.com.au/",
|
|
||||||
"https://www.coles.com.au/",
|
|
||||||
"https://www.abc.net.au/",
|
|
||||||
"https://www.news.com.au/",
|
|
||||||
"https://my.gov.au/",
|
|
||||||
"https://www.ato.gov.au/",
|
|
||||||
"https://www.commbank.com.au/",
|
|
||||||
"https://www.suncorp.com.au/",
|
|
||||||
"https://www.boq.com.au/",
|
|
||||||
"https://www.realestate.com.au/",
|
|
||||||
"https://www.domain.com.au/",
|
|
||||||
"https://www.seek.com.au/",
|
|
||||||
"https://www.nrl.com/",
|
|
||||||
"https://premier.ticketek.com.au/",
|
|
||||||
"https://www.amazon.com.au/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Australia - Adelaide",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": -34.9227,
|
|
||||||
"base_lon": 138.6016,
|
|
||||||
"lang_params": "hl=en-AU&gl=AU",
|
|
||||||
"valid_url_suffix": "com.au"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.adelaidenow.com.au/",
|
|
||||||
"https://indaily.com.au/",
|
|
||||||
"https://www.sa.gov.au/",
|
|
||||||
"https://www.adelaidemetro.com.au/",
|
|
||||||
"https://www.sapowernetworks.com.au/",
|
|
||||||
"https://www.bom.gov.au/",
|
|
||||||
"https://www.bunnings.com.au/",
|
|
||||||
"https://www.coles.com.au/",
|
|
||||||
"https://www.woolworths.com.au/",
|
|
||||||
"https://www.abc.net.au/",
|
|
||||||
"https://www.news.com.au/",
|
|
||||||
"https://my.gov.au/",
|
|
||||||
"https://www.ato.gov.au/",
|
|
||||||
"https://www.commbank.com.au/",
|
|
||||||
"https://www.bendigobank.com.au/",
|
|
||||||
"https://www.realestate.com.au/",
|
|
||||||
"https://www.domain.com.au/",
|
|
||||||
"https://www.seek.com.au/",
|
|
||||||
"https://www.afl.com.au/",
|
|
||||||
"https://premier.ticketek.com.au/",
|
|
||||||
"https://www.amazon.com.au/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Australia - Melbourne",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": -37.8106,
|
|
||||||
"base_lon": 144.9624,
|
|
||||||
"lang_params": "hl=en-AU&gl=AU",
|
|
||||||
"valid_url_suffix": "com.au"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.theage.com.au/",
|
|
||||||
"https://www.heraldsun.com.au/",
|
|
||||||
"https://www.vic.gov.au/",
|
|
||||||
"https://www.ptv.vic.gov.au/",
|
|
||||||
"https://www.vicroads.vic.gov.au/",
|
|
||||||
"https://www.bom.gov.au/",
|
|
||||||
"https://www.bunnings.com.au/",
|
|
||||||
"https://www.kmart.com.au/",
|
|
||||||
"https://www.abc.net.au/",
|
|
||||||
"https://www.news.com.au/",
|
|
||||||
"https://my.gov.au/",
|
|
||||||
"https://www.ato.gov.au/",
|
|
||||||
"https://www.nab.com.au/",
|
|
||||||
"https://www.anz.com.au/",
|
|
||||||
"https://www.woolworths.com.au/",
|
|
||||||
"https://www.coles.com.au/",
|
|
||||||
"https://www.realestate.com.au/",
|
|
||||||
"https://www.seek.com.au/",
|
|
||||||
"https://www.afl.com.au/",
|
|
||||||
"https://www.ticketmaster.com.au/",
|
|
||||||
"https://www.amazon.com.au/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Australia - Perth",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": -31.9545,
|
|
||||||
"base_lon": 115.8582,
|
|
||||||
"lang_params": "hl=en-AU&gl=AU",
|
|
||||||
"valid_url_suffix": "com.au"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://thewest.com.au/",
|
|
||||||
"https://www.perthnow.com.au/",
|
|
||||||
"https://www.wa.gov.au/",
|
|
||||||
"https://www.transperth.wa.gov.au/",
|
|
||||||
"https://www.bom.gov.au/",
|
|
||||||
"https://www.bunnings.com.au/",
|
|
||||||
"https://www.coles.com.au/",
|
|
||||||
"https://www.woolworths.com.au/",
|
|
||||||
"https://www.kmart.com.au/",
|
|
||||||
"https://www.abc.net.au/",
|
|
||||||
"https://www.news.com.au/",
|
|
||||||
"https://my.gov.au/",
|
|
||||||
"https://www.ato.gov.au/",
|
|
||||||
"https://www.commbank.com.au/",
|
|
||||||
"https://www.bankwest.com.au/",
|
|
||||||
"https://reiwa.com.au/",
|
|
||||||
"https://www.realestate.com.au/",
|
|
||||||
"https://www.seek.com.au/",
|
|
||||||
"https://www.afl.com.au/",
|
|
||||||
"https://premier.ticketek.com.au/",
|
|
||||||
"https://www.amazon.com.au/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Canada - Montreal",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 45.5017,
|
|
||||||
"base_lon": -73.5673,
|
|
||||||
"lang_params": "hl=en&gl=CA",
|
|
||||||
"valid_url_suffix": "ca"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.cbc.ca/",
|
|
||||||
"https://www.amazon.ca/",
|
|
||||||
"https://www.theweathernetwork.com/ca"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Canada - Toronto",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 43.6532,
|
|
||||||
"base_lon": -79.3832,
|
|
||||||
"lang_params": "hl=en&gl=CA",
|
|
||||||
"valid_url_suffix": "ca"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.canada.ca/en.html",
|
|
||||||
"https://www.cbc.ca/",
|
|
||||||
"https://www.thestar.com/",
|
|
||||||
"https://www.ctvnews.ca/",
|
|
||||||
"https://www.canadapost-postescanada.ca/",
|
|
||||||
"https://www.td.com/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Spain - Madrid",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 40.4168,
|
|
||||||
"base_lon": -3.7038,
|
|
||||||
"lang_params": "hl=es&gl=ES",
|
|
||||||
"valid_url_suffix": "es"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://es.wikipedia.org/wiki/Especial:Aleatoria",
|
|
||||||
"https://www.elmundo.es/",
|
|
||||||
"https://www.elpais.com/",
|
|
||||||
"https://www.marca.com/",
|
|
||||||
"https://www.rtve.es/",
|
|
||||||
"https://www.zara.com/es/",
|
|
||||||
"https://www.elcorteingles.es/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "South Korea - Seoul",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 37.5665,
|
|
||||||
"base_lon": 126.9780,
|
|
||||||
"lang_params": "hl=ko&gl=KR",
|
|
||||||
"valid_url_suffix": "co.kr"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://ko.wikipedia.org/wiki/특수:임의문서",
|
|
||||||
"https://www.naver.com/",
|
|
||||||
"https://www.daum.net/",
|
|
||||||
"https://namu.wiki/",
|
|
||||||
"https://www.tistory.com/",
|
|
||||||
"https://www.coupang.com/",
|
|
||||||
"https://www.chosun.com/",
|
|
||||||
"https://www.yna.co.kr/",
|
|
||||||
"https://www.kakaocorp.com/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Netherlands - Amsterdam",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 52.3676,
|
|
||||||
"base_lon": 4.9041,
|
|
||||||
"lang_params": "hl=nl&gl=NL",
|
|
||||||
"valid_url_suffix": "nl"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://nl.wikipedia.org/wiki/Speciaal:Willekeurig",
|
|
||||||
"https://www.rijksoverheid.nl/",
|
|
||||||
"https://www.nos.nl/",
|
|
||||||
"https://www.bol.com/",
|
|
||||||
"https://www.nu.nl/",
|
|
||||||
"https://www.buienradar.nl/",
|
|
||||||
"https://www.telegraaf.nl/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Taiwan - Taipei",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 25.0330,
|
|
||||||
"base_lon": 121.5654,
|
|
||||||
"lang_params": "hl=zh-TW&gl=TW",
|
|
||||||
"valid_url_suffix": "com.tw"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://zh.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://tw.yahoo.com/",
|
|
||||||
"https://www.pchome.com.tw/",
|
|
||||||
"https://www.momoshop.com.tw/",
|
|
||||||
"https://www.ruten.com.tw/",
|
|
||||||
"https://www.mobile01.com/",
|
|
||||||
"https://www.dcard.tw/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United Kingdom - Coventry",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 52.4068,
|
|
||||||
"base_lon": -1.5197,
|
|
||||||
"lang_params": "hl=en&gl=GB",
|
|
||||||
"valid_url_suffix": "co.uk"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://en.wikipedia.org/wiki/Special:Random",
|
|
||||||
"https://www.bbc.co.uk/",
|
|
||||||
"https://www.amazon.co.uk/",
|
|
||||||
"https://www.theguardian.com/uk"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Warrenville",
|
|
||||||
"google_module": { "base_lat": 41.8164, "base_lon": -88.1748, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Charlotte",
|
|
||||||
"google_module": { "base_lat": 35.2271, "base_lon": -80.8431, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Las Vegas",
|
|
||||||
"google_module": { "base_lat": 36.1699, "base_lon": -115.1398, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Bend",
|
|
||||||
"google_module": { "base_lat": 44.0582, "base_lon": -121.3153, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Salt Lake City",
|
|
||||||
"google_module": { "base_lat": 40.7608, "base_lon": -111.8910, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "United States - Seattle",
|
|
||||||
"google_module": { "base_lat": 47.6062, "base_lon": -122.3321, "lang_params": "hl=en&gl=US", "valid_url_suffix": "com" },
|
|
||||||
"trust_module": { "white_urls": [ "https://en.wikipedia.org/wiki/Special:Random", "https://www.yahoo.com/", "https://www.target.com/", "https://www.npr.org/", "https://www.weather.com/", "https://www.amazon.com/", "https://www.cdc.gov/" ] }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"region_name": "Vietnam - Hanoi",
|
|
||||||
"google_module": {
|
|
||||||
"base_lat": 21.0285,
|
|
||||||
"base_lon": 105.8542,
|
|
||||||
"lang_params": "hl=vi&gl=VN",
|
|
||||||
"valid_url_suffix": "vn"
|
|
||||||
},
|
|
||||||
"trust_module": {
|
|
||||||
"white_urls": [
|
|
||||||
"https://vi.wikipedia.org/wiki/Đặc_biệt:Ngẫu_nhiên",
|
|
||||||
"https://chinhphu.vn/",
|
|
||||||
"https://vnexpress.net/",
|
|
||||||
"https://tuoitre.vn/",
|
|
||||||
"https://vtv.vn/",
|
|
||||||
"https://shopee.vn/",
|
|
||||||
"https://tiki.vn/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +1,64 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: install_master.sh (IP-Sentinel 控制中枢部署脚本 - 动态锚点版)
|
# 脚本名称: install_master.sh (IP-Sentinel 控制中枢部署脚本 v3.2.3)
|
||||||
# 核心功能: 部署/卸载调度中枢、SQLite 资产管理、平滑热更新引擎
|
# 核心功能: 部署/卸载调度中枢、SQLite 资产管理、平滑热更新引擎
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# ==========================================================
|
# [新增] 提取仓库直链前缀变量,方便后续在官方库和私库间一键切换
|
||||||
# 🛑 核心权限防线: 检查是否以 root 权限运行
|
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/legacy"
|
||||||
# ==========================================================
|
# 临时改为私库地址用于测试
|
||||||
if [ "$EUID" -ne 0 ]; then
|
# REPO_RAW_URL="https://git.94211762.xyz/hotyue/IP-Sentinel/raw/branch/main"
|
||||||
echo -e "\033[31m❌ 权限被拒绝: 部署 IP-Sentinel 需要最高系统权限。\033[0m"
|
|
||||||
echo -e "💡 请切换到 root 用户 (执行 su root 或 sudo -i) 后重新运行指令。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 你的 GitHub 仓库 Raw 数据直链前缀
|
|
||||||
REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/main"
|
|
||||||
# 临时改为开发地址用于测试
|
|
||||||
# REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/v3.6.2-rc"
|
|
||||||
|
|
||||||
# [核心: 动态提取 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 (控制中枢) v${TARGET_VERSION}"
|
echo " 🧠 欢迎使用 IP-Sentinel Master (控制中枢) v3.2.2"
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
|
|
||||||
# ==========================================================
|
# [新增] 交互式操作菜单:支持选择部署或调用卸载程序
|
||||||
# [v3.6.1 核心] 拦截司令部静默 OTA 升级模式 (强行接管执行流)
|
echo -e "\n请选择操作:"
|
||||||
# ==========================================================
|
echo " 1) 🚀 部署 Master 控制中枢"
|
||||||
if [ "$SILENT_MASTER_OTA" == "true" ]; then
|
echo " 2) 🗑️ 一键卸载 Master 中枢"
|
||||||
echo -e "\n⏳ [OTA] 中枢重构指令已确认,正在剥离控制台交互..."
|
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
||||||
ACTION_CHOICE=1
|
|
||||||
UPGRADE_MODE="true"
|
if [ "$ACTION_CHOICE" == "2" ]; then
|
||||||
KEEP_DB="true"
|
echo -e "\n⏳ 正在拉取卸载程序..."
|
||||||
|
# [新增逻辑] 使用上面定义的 REPO_RAW_URL 动态拉取卸载脚本,执行后自动销毁临时文件
|
||||||
# 汲取原配置进入内存
|
curl -sL "${REPO_RAW_URL}/master/uninstall_master.sh" -o "/tmp/uninstall_master.sh"
|
||||||
if [ -f "${MASTER_DIR}/master.conf" ]; then
|
chmod +x "/tmp/uninstall_master.sh"
|
||||||
source "${MASTER_DIR}/master.conf"
|
bash "/tmp/uninstall_master.sh"
|
||||||
|
rm -f "/tmp/uninstall_master.sh"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
||||||
|
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
|
||||||
|
|
||||||
# 同步新版本号至配置文件
|
# 汲取原配置进入内存
|
||||||
if grep -q "^MASTER_VERSION=" "${MASTER_DIR}/master.conf"; then
|
source "${MASTER_DIR}/master.conf"
|
||||||
sed -i "s/^MASTER_VERSION=.*/MASTER_VERSION=\"$TARGET_VERSION\"/" "${MASTER_DIR}/master.conf"
|
echo -e "\033[32m✅ 已激活 [平滑升级模式],即将跳过基础配置,直接更新核心中枢...\033[0m"
|
||||||
else
|
else
|
||||||
echo "MASTER_VERSION=\"$TARGET_VERSION\"" >> "${MASTER_DIR}/master.conf"
|
echo -e "\033[33m🔄 您选择了重新配置,旧的中枢数据将被彻底抹除。\033[0m"
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo -e "\033[32m✅ 已激活 [中枢静默重构模式],即将无损覆写内核...\033[0m"
|
|
||||||
else
|
|
||||||
# [新增] 交互式操作菜单:支持选择部署或调用卸载程序
|
|
||||||
echo -e "\n请选择操作:"
|
|
||||||
echo " 1) 🚀 部署 Master 控制中枢"
|
|
||||||
echo " 2) 🗑️ 一键卸载 Master 中枢"
|
|
||||||
read -p "请输入选择 [1-2] (默认1): " ACTION_CHOICE
|
|
||||||
|
|
||||||
# [v3.5.2 修复] 防止用户直接回车导致变量为空,从而漏过下方的平滑升级判定被误删档
|
|
||||||
ACTION_CHOICE=${ACTION_CHOICE:-1}
|
|
||||||
|
|
||||||
if [ "$ACTION_CHOICE" == "2" ]; then
|
|
||||||
echo -e "\n⏳ 正在拉取卸载程序..."
|
|
||||||
curl -sL "${REPO_RAW_URL}/master/uninstall_master.sh" -o "/tmp/uninstall_master.sh"
|
|
||||||
chmod +x "/tmp/uninstall_master.sh"
|
|
||||||
bash "/tmp/uninstall_master.sh"
|
|
||||||
rm -f "/tmp/uninstall_master.sh"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ================== [v3.2.2 新增: 平滑升级模式嗅探] ==================
|
|
||||||
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"
|
|
||||||
|
|
||||||
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
|
fi
|
||||||
fi
|
fi
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
# ================== [v3.2.2 优化: 安装前环境纯净度清理与数据保护] ==================
|
# ================== [v3.2.2 优化: 安装前环境纯净度清理与数据保护] ==================
|
||||||
echo -e "\n⏳ 正在清理旧版 Master 守护进程..."
|
echo -e "\n⏳ 正在清理旧版 Master 守护进程..."
|
||||||
# [新增] 优雅停止 Systemd 服务,防止代码替换时引发无限复活风暴
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
systemctl stop ip-sentinel-master.service >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
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 [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
@@ -127,67 +77,14 @@ fi
|
|||||||
echo -e "\033[32m✅ 旧进程已肃清!\033[0m"
|
echo -e "\033[32m✅ 旧进程已肃清!\033[0m"
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
|
|
||||||
# 1. 依赖检查与智能安装 (v3.6.0 兼容性与优雅性升级)
|
# 1. 环境依赖安装
|
||||||
echo -e "\n[1/4] 正在探测核心依赖 (curl, jq, sqlite3, crontab, pgrep)..."
|
echo -e "\n[1/4] 安装核心依赖 (curl, jq, sqlite3)..."
|
||||||
|
if [ -f /etc/debian_version ]; then
|
||||||
REQUIRED_CMDS=("curl" "jq" "sqlite3" "crontab" "pgrep")
|
apt-get update -y >/dev/null 2>&1
|
||||||
MISSING_CMDS=()
|
apt-get install -y curl jq sqlite3 procps >/dev/null 2>&1
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
# 基础探测:预检查缺失的命令
|
yum install -y curl jq sqlite >/dev/null 2>&1
|
||||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
MISSING_CMDS+=("$cmd")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 如果有缺失,才执行包管理器拉取逻辑
|
|
||||||
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
|
||||||
echo "⏳ 发现缺失依赖: ${MISSING_CMDS[*]},正在尝试自动补齐..."
|
|
||||||
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
|
||||||
apt-get update -y >/dev/null 2>&1
|
|
||||||
# [v3.6.3 抽脂级优化] 注入 --no-install-recommends 拒绝捆绑销售
|
|
||||||
apt-get install -y --no-install-recommends curl jq sqlite3 cron procps >/dev/null 2>&1
|
|
||||||
systemctl enable cron >/dev/null 2>&1 && systemctl start cron >/dev/null 2>&1
|
|
||||||
elif command -v yum >/dev/null 2>&1 || command -v dnf >/dev/null 2>&1; then
|
|
||||||
PKG_MGR="yum"
|
|
||||||
OPT_ARGS=""
|
|
||||||
if command -v dnf >/dev/null 2>&1; then
|
|
||||||
PKG_MGR="dnf"
|
|
||||||
# [v3.6.3 抽脂级优化] 强行关闭 DNF 的弱依赖拉取
|
|
||||||
OPT_ARGS="--setopt=install_weak_deps=False"
|
|
||||||
fi
|
|
||||||
$PKG_MGR install -y $OPT_ARGS curl jq sqlite cronie procps-ng >/dev/null 2>&1
|
|
||||||
systemctl enable crond >/dev/null 2>&1 && systemctl start crond >/dev/null 2>&1
|
|
||||||
elif command -v apk >/dev/null 2>&1; then
|
|
||||||
echo "Alpine 探测到系统类型为 Alpine Linux,正在执行轻量级安装..."
|
|
||||||
apk add --no-cache curl jq sqlite dcron procps bash >/dev/null 2>&1
|
|
||||||
mkdir -p /var/spool/cron/crontabs
|
|
||||||
rc-update add crond default >/dev/null 2>&1
|
|
||||||
service crond start >/dev/null 2>&1
|
|
||||||
elif command -v pacman >/dev/null 2>&1; then
|
|
||||||
pacman -Sy --noconfirm curl jq sqlite cronie procps-ng >/dev/null 2>&1
|
|
||||||
mkdir -p /root/.cache/crontab 2>/dev/null
|
|
||||||
systemctl enable cronie >/dev/null 2>&1 && systemctl start cronie >/dev/null 2>&1
|
|
||||||
else
|
|
||||||
echo -e "\033[31m❌ 自动安装失败:系统未知的包管理器。\033[0m"
|
|
||||||
echo -e "\033[33m⚠️ 请手动执行以下安装命令后重新运行本脚本:\033[0m"
|
|
||||||
echo -e " Debian/Ubuntu: \033[36mapt-get update && apt-get install -y --no-install-recommends curl jq sqlite3 cron procps\033[0m"
|
|
||||||
echo -e " CentOS/RHEL: \033[36myum install -y curl jq sqlite cronie procps-ng\033[0m"
|
|
||||||
echo -e " Alpine Linux: \033[36mapk add --no-cache curl jq sqlite dcron procps bash\033[0m"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 安装后二次复检
|
|
||||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
echo -e "\033[31m❌ 致命错误:核心命令 '$cmd' 仍未找到!\033[0m"
|
|
||||||
echo -e "请手动修复您的包管理器源,或联系 VPS 供应商。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
echo -e "\033[32m✅ 基础环境检测通过。\033[0m"
|
|
||||||
|
|
||||||
mkdir -p "$MASTER_DIR"
|
mkdir -p "$MASTER_DIR"
|
||||||
|
|
||||||
@@ -198,58 +95,16 @@ if [ "$UPGRADE_MODE" == "false" ]; then
|
|||||||
# 2. 交互配置机器人
|
# 2. 交互配置机器人
|
||||||
echo -e "\n[2/4] 配置控制中枢机器人:"
|
echo -e "\n[2/4] 配置控制中枢机器人:"
|
||||||
read -p "请输入 Telegram Bot Token: " TG_TOKEN
|
read -p "请输入 Telegram Bot Token: " TG_TOKEN
|
||||||
|
|
||||||
# [v3.6.0 新增] 官方网关模式选项 (用于屏蔽全局 OTA 按钮)
|
|
||||||
echo -e "\n请选择您的部署环境身份:"
|
|
||||||
echo " 1) 🛡️ 私有独立中枢 (默认推荐,保留完整 OTA 遥控权限)"
|
|
||||||
echo " 2) ☁️ 官方公共网关 (面向大众服务,将强制物理隐藏全局 OTA 按钮防滥用)"
|
|
||||||
read -p "请输入选择 [1-2] (默认1): " GATEWAY_TYPE
|
|
||||||
GATEWAY_TYPE=${GATEWAY_TYPE:-1}
|
|
||||||
|
|
||||||
IS_OFFICIAL_GATEWAY="false"
|
|
||||||
ENABLE_MASTER_OTA="false"
|
|
||||||
if [ "$GATEWAY_TYPE" == "2" ]; then
|
|
||||||
IS_OFFICIAL_GATEWAY="true"
|
|
||||||
echo -e "\033[33m⚠️ 已开启官方公共网关模式,全舰队与司令部的 OTA 将被强制屏蔽。\033[0m"
|
|
||||||
else
|
|
||||||
# [v3.6.1] 私有模式开放中枢 OTA 授权向导
|
|
||||||
echo -e "\n[2.1/4] 司令部自我进化授权"
|
|
||||||
echo -e "💡 开启后,您可以在 TG 菜单一键将中枢核心系统热更新至最新版本。"
|
|
||||||
read -p "是否允许司令部接收 OTA 重构指令?(y/n, 默认y): " M_OTA_CHOICE
|
|
||||||
if [[ "$M_OTA_CHOICE" =~ ^[Nn]$ ]]; then
|
|
||||||
ENABLE_MASTER_OTA="false"
|
|
||||||
echo -e "🛡️ \033[33m已关闭司令部 OTA 权限,中枢内核未来仅支持 SSH 升级。\033[0m"
|
|
||||||
else
|
|
||||||
ENABLE_MASTER_OTA="true"
|
|
||||||
echo -e "✅ \033[32m已开启司令部 OTA 权限,金蝉脱壳引信已挂载。\033[0m"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
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"
|
||||||
# [v3.6.0 核心] 官方网关 UI 熔断标识
|
|
||||||
IS_OFFICIAL_GATEWAY="$IS_OFFICIAL_GATEWAY"
|
|
||||||
# [v3.6.1 新增] 司令部自身 OTA 授权标识
|
|
||||||
ENABLE_MASTER_OTA="$ENABLE_MASTER_OTA"
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# [v3.6.1 热修复] 老司令部平滑升级时,自动补齐缺失字段
|
|
||||||
if [ "$UPGRADE_MODE" == "true" ]; then
|
|
||||||
if ! grep -q "^IS_OFFICIAL_GATEWAY=" "${MASTER_DIR}/master.conf"; then
|
|
||||||
echo "IS_OFFICIAL_GATEWAY=\"false\"" >> "${MASTER_DIR}/master.conf"
|
|
||||||
fi
|
|
||||||
if ! grep -q "^ENABLE_MASTER_OTA=" "${MASTER_DIR}/master.conf"; then
|
|
||||||
echo "ENABLE_MASTER_OTA=\"false\"" >> "${MASTER_DIR}/master.conf"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# 🛑 拦截块结束
|
# 🛑 拦截块结束
|
||||||
|
|
||||||
# 3. 初始化 SQLite 数据库 (幂等操作,升级模式下由 tg_master.sh 负责热修补)
|
# 3. 初始化 SQLite 数据库 (幂等操作,升级模式下可安全修补表结构)
|
||||||
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 (
|
||||||
@@ -258,11 +113,6 @@ 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',
|
|
||||||
enable_ota TEXT DEFAULT 'false',
|
|
||||||
PRIMARY KEY(chat_id, node_name)
|
PRIMARY KEY(chat_id, node_name)
|
||||||
);
|
);
|
||||||
EOF
|
EOF
|
||||||
@@ -279,64 +129,20 @@ echo -e "\n[4/4] 部署 TG 调度守护进程..."
|
|||||||
curl -sL "${REPO_RAW_URL}/master/tg_master.sh" -o "${MASTER_DIR}/tg_master.sh"
|
curl -sL "${REPO_RAW_URL}/master/tg_master.sh" -o "${MASTER_DIR}/tg_master.sh"
|
||||||
chmod +x "${MASTER_DIR}/tg_master.sh"
|
chmod +x "${MASTER_DIR}/tg_master.sh"
|
||||||
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
# 写入看门狗 Cron
|
||||||
echo "💡 检测到 Systemd 环境,正在部署原生守护服务..."
|
crontab -l 2>/dev/null | grep -v "tg_master.sh" > /tmp/cron_master
|
||||||
|
echo "* * * * * pgrep -f tg_master.sh >/dev/null || nohup bash ${MASTER_DIR}/tg_master.sh >/dev/null 2>&1 &" >> /tmp/cron_master
|
||||||
cat > /etc/systemd/system/ip-sentinel-master.service << EOF
|
crontab /tmp/cron_master
|
||||||
[Unit]
|
rm -f /tmp/cron_master
|
||||||
Description=IP-Sentinel Master Command Center Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
# 立刻启动
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
pgrep -f tg_master.sh >/dev/null || nohup bash "${MASTER_DIR}/tg_master.sh" >/dev/null 2>&1 &
|
||||||
SyslogIdentifier=ip-sentinel
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/bin/bash ${MASTER_DIR}/tg_master.sh
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=${MASTER_DIR}
|
|
||||||
CPUSchedulingPolicy=idle
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
|
|
||||||
[Install]
|
# ================== [v3.2.2 优化: 战报文案分流] ==================
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable --now ip-sentinel-master.service
|
|
||||||
systemctl restart ip-sentinel-master.service
|
|
||||||
|
|
||||||
# 清理可能残留的历史 Cron
|
|
||||||
crontab -l 2>/dev/null | grep -v "tg_master.sh" > /tmp/cron_master || true
|
|
||||||
[ -f /tmp/cron_master ] && crontab /tmp/cron_master 2>/dev/null
|
|
||||||
rm -f /tmp/cron_master
|
|
||||||
else
|
|
||||||
echo "💡 未检测到 Systemd,回退到 Cron 看门狗调度模式..."
|
|
||||||
crontab -l 2>/dev/null | grep -v "tg_master.sh" > /tmp/cron_master || true
|
|
||||||
echo "* * * * * pgrep -f tg_master.sh >/dev/null || nohup bash ${MASTER_DIR}/tg_master.sh >/dev/null 2>&1 &" >> /tmp/cron_master
|
|
||||||
[ -f /tmp/cron_master ] && crontab /tmp/cron_master 2>/dev/null
|
|
||||||
rm -f /tmp/cron_master
|
|
||||||
|
|
||||||
pgrep -f tg_master.sh >/dev/null || { nohup bash "${MASTER_DIR}/tg_master.sh" >/dev/null 2>&1 & disown 2>/dev/null; }
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ================== [v3.2.2 优化 & v3.6.1 OTA捷报: 战报文案分流] ==================
|
|
||||||
echo "========================================================"
|
echo "========================================================"
|
||||||
if [ "$UPGRADE_MODE" == "true" ]; then
|
if [ "$UPGRADE_MODE" == "true" ]; then
|
||||||
echo "🎉 Master 控制中枢平滑热更新完成!"
|
echo "🎉 Master 控制中枢平滑热更新完成!"
|
||||||
echo "🤖 新版中枢引擎已接管数据库,继续等待边缘节点汇报。"
|
echo "🤖 新版中枢引擎已接管数据库,继续等待边缘节点汇报。"
|
||||||
|
|
||||||
# [v3.6.1 核心] 静默 OTA 完成后,由幽灵进程主动向指挥官发送捷报
|
|
||||||
if [ "$SILENT_MASTER_OTA" == "true" ] && [ -n "$OTA_CHAT_ID" ] && [ -n "$TG_TOKEN" ]; then
|
|
||||||
echo -e "\n📡 正在向指挥官发送司令部重构捷报..."
|
|
||||||
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
|
||||||
-d "chat_id=${OTA_CHAT_ID}" \
|
|
||||||
-d "parse_mode=Markdown" \
|
|
||||||
-d "text=✨ *司令部中枢热重载完成!*
|
|
||||||
🚀 当前内核已跃升至:\`v${TARGET_VERSION}\`
|
|
||||||
🤖 新版金蝉脱壳引擎已接管阵地,全舰队指控链路恢复正常。" > /dev/null
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "🎉 Master 控制中枢部署完成!"
|
echo "🎉 Master 控制中枢部署完成!"
|
||||||
echo "🤖 机器人现已开始全局接客,等待边缘节点注册。"
|
echo "🤖 机器人现已开始全局接客,等待边缘节点注册。"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: tg_master.sh (Master 端调度枢纽 - 动态锚点版)
|
# 脚本名称: tg_master.sh (Master 端调度枢纽 V3.0.4 动态签名版)
|
||||||
# 核心功能: 监听 TG、操作 SQLite、Webhook 精准调度、403权限拦截、僵尸节点清理
|
# 核心功能: 监听 TG、操作 SQLite、Webhook 精准调度、403权限拦截、僵尸节点清理
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
@@ -9,45 +9,30 @@ 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"
|
|
||||||
# 临时改为开发地址用于测试
|
|
||||||
# REPO_RAW_URL="https://raw.githubusercontent.com/hotyue/IP-Sentinel/v3.6.2-rc"
|
|
||||||
# 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
|
||||||
|
|
||||||
# --- 工具函数 ---
|
# --- 工具函数 ---
|
||||||
send_ui() {
|
send_ui() {
|
||||||
curl -s --connect-timeout 5 -m 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"chat_id\":\"$1\",\"text\":\"$2\",\"parse_mode\":\"Markdown\",\"reply_markup\":{\"inline_keyboard\":$3}}" > /dev/null
|
-d "{\"chat_id\":\"$1\",\"text\":\"$2\",\"parse_mode\":\"Markdown\",\"reply_markup\":{\"inline_keyboard\":$3}}" > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
send_msg() {
|
send_msg() {
|
||||||
curl -s --connect-timeout 5 -m 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
||||||
-d "chat_id=$1" -d "text=$2" -d "parse_mode=Markdown" > /dev/null
|
-d "chat_id=$1" -d "text=$2" -d "parse_mode=Markdown" > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# ================== [v3.0.1 新增: 消息原位刷新函数] ==================
|
# ================== [v3.0.1 新增: 消息原位刷新函数] ==================
|
||||||
edit_msg() {
|
edit_msg() {
|
||||||
curl -s --connect-timeout 5 -m 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/editMessageText" \
|
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/editMessageText" \
|
||||||
-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 --connect-timeout 5 -m 10 -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
|
|
||||||
}
|
|
||||||
|
|
||||||
# 数据库执行函数 (v3.6.3 终极静默版: 动用 .timeout 点命令防泄露)
|
|
||||||
db_exec() {
|
db_exec() {
|
||||||
printf ".timeout 5000\n%s\n" "$1" | sqlite3 "$DB_FILE"
|
sqlite3 "$DB_FILE" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ================== [v3.0.4 核心: 动态 HMAC 签名生成器] ==================
|
# ================== [v3.0.4 核心: 动态 HMAC 签名生成器] ==================
|
||||||
@@ -65,28 +50,19 @@ generate_signed_url() {
|
|||||||
local signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$CHAT_ID" | awk '{print $NF}')
|
local signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$CHAT_ID" | awk '{print $NF}')
|
||||||
|
|
||||||
# 返回最终带签名的 URL
|
# 返回最终带签名的 URL
|
||||||
echo "https://${target_ip}:${target_port}${action_path}?t=${current_t}&sign=${signature}"
|
echo "http://${target_ip}:${target_port}${action_path}?t=${current_t}&sign=${signature}"
|
||||||
}
|
}
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
# ================== [v3.6.3 核心: 激活 SQLite 高并发 WAL 引擎] ==================
|
# ================== [v3.1.3 核心: 数据库结构无损热升级] ==================
|
||||||
db_exec "PRAGMA journal_mode=WAL;" > /dev/null 2>&1
|
# 自动探测并增加 region 字段,屏蔽已存在的报错,保护老节点数据
|
||||||
db_exec "PRAGMA synchronous=NORMAL;" > /dev/null 2>&1
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
# ================== [v3.1.3-v3.6.0 核心: 数据库结构无损热升级] ==================
|
|
||||||
# 自动探测并增加缺失字段,屏蔽已存在的报错,保护老节点数据
|
|
||||||
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
|
|
||||||
db_exec "ALTER TABLE nodes ADD COLUMN enable_ota TEXT DEFAULT 'false';" 2>/dev/null
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
# --- 核心轮询循环 ---
|
# --- 核心轮询循环 ---
|
||||||
while true; do
|
while true; do
|
||||||
OFFSET=$(cat $OFFSET_FILE)
|
OFFSET=$(cat $OFFSET_FILE)
|
||||||
UPDATES=$(curl -s --connect-timeout 5 -m 35 "https://api.telegram.org/bot${TG_TOKEN}/getUpdates?offset=${OFFSET}&timeout=30")
|
UPDATES=$(curl -s "https://api.telegram.org/bot${TG_TOKEN}/getUpdates?offset=${OFFSET}&timeout=30")
|
||||||
|
|
||||||
COUNT=$(echo "$UPDATES" | jq -r '.result | length' 2>/dev/null)
|
COUNT=$(echo "$UPDATES" | jq -r '.result | length' 2>/dev/null)
|
||||||
|
|
||||||
@@ -97,23 +73,6 @@ 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')
|
||||||
@@ -121,7 +80,7 @@ while true; do
|
|||||||
|
|
||||||
# 告诉 TG 官方“指令已收到”,立刻消除按钮上的加载圈圈
|
# 告诉 TG 官方“指令已收到”,立刻消除按钮上的加载圈圈
|
||||||
if [ -n "$CB_ID" ]; then
|
if [ -n "$CB_ID" ]; then
|
||||||
curl -s --connect-timeout 5 -m 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/answerCallbackQuery" -d "callback_query_id=${CB_ID}" > /dev/null
|
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/answerCallbackQuery" -d "callback_query_id=${CB_ID}" > /dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -130,34 +89,21 @@ 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.6.0 兼容性拆包: 支持 7字段(OTA)、6字段(双轨)、5字段(单轨)、4字段(远古)
|
# V3.1.3 兼容性拆包: 判断是新版协议 (5个字段) 还是老版协议 (4个字段)
|
||||||
FIELD_COUNT=$(echo "$REG_LINE" | awk -F'|' '{print NF}')
|
FIELD_COUNT=$(echo "$REG_LINE" | awk -F'|' '{print NF}')
|
||||||
if [ "$FIELD_COUNT" -ge 7 ]; then
|
if [ "$FIELD_COUNT" -ge 5 ]; then
|
||||||
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT RAW_ALIAS RAW_OTA <<< "$REG_LINE"
|
|
||||||
elif [ "$FIELD_COUNT" -eq 6 ]; then
|
|
||||||
IFS='|' read -r MAGIC RAW_REGION RAW_NODE RAW_IP RAW_PORT RAW_ALIAS <<< "$REG_LINE"
|
|
||||||
RAW_OTA="false"
|
|
||||||
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"
|
|
||||||
RAW_OTA="false"
|
|
||||||
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"
|
|
||||||
RAW_OTA="false"
|
|
||||||
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"
|
|
||||||
AGENT_OTA=$(echo "$RAW_OTA" | tr -cd 'a-z')
|
|
||||||
[ -z "$AGENT_OTA" ] && AGENT_OTA="false"
|
|
||||||
|
|
||||||
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 攻击渗透。"
|
||||||
@@ -169,9 +115,9 @@ while true; do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# [核心] 入库时追加 node_alias 与 enable_ota 字段
|
# 入库时追加 region 字段
|
||||||
db_exec "INSERT INTO nodes (chat_id, node_name, agent_ip, agent_port, last_seen, region, node_alias, enable_ota) VALUES ('$CHAT_ID', '$NODE_NAME', '$AGENT_IP', '$AGENT_PORT', CURRENT_TIMESTAMP, '$AGENT_REGION', '$NODE_ALIAS', '$AGENT_OTA') 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', enable_ota='$AGENT_OTA';"
|
db_exec "INSERT INTO nodes (chat_id, node_name, agent_ip, agent_port, last_seen, region) VALUES ('$CHAT_ID', '$NODE_NAME', '$AGENT_IP', '$AGENT_PORT', CURRENT_TIMESTAMP, '$AGENT_REGION') ON CONFLICT(chat_id, node_name) DO UPDATE SET agent_ip='$AGENT_IP', agent_port='$AGENT_PORT', last_seen=CURRENT_TIMESTAMP, region='$AGENT_REGION';"
|
||||||
send_msg "$CHAT_ID" "✅ **司令部确认 (v${MASTER_VERSION})**%0A节点 \`${NODE_ALIAS}\` 档案已录入!"
|
send_msg "$CHAT_ID" "✅ 司令部已确认!节点接入成功: \`$NODE_NAME\` ($AGENT_IP:$AGENT_PORT)"
|
||||||
|
|
||||||
# ================== [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;")
|
||||||
@@ -183,8 +129,6 @@ 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"
|
||||||
@@ -201,97 +145,8 @@ while true; do
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
case "$TEXT" in
|
case "$TEXT" in
|
||||||
"/start"|"/menu")
|
"/start"|"/menu")
|
||||||
# [核心: 抓取云端最新 Master 版本 (KV 解析法)]
|
BTNS="[[{\"text\":\"🖥️ 我的节点列表\",\"callback_data\":\"list_nodes\"}], [{\"text\":\"🚀 全节点日报汇总\",\"callback_data\":\"all_reports\"}], [{\"text\":\"🛠️ 全节点一键维护\",\"callback_data\":\"all_run\"}]]"
|
||||||
REMOTE_VER=$(curl -s -m 2 "${REPO_RAW_URL}/version.txt" | grep "^MASTER_VERSION=" | cut -d'=' -f2 | tr -d '[:space:]')
|
send_ui "$CHAT_ID" "🛡️ **IP-Sentinel 司令部**\n欢迎回来,长官。请下达指令:" "$BTNS"
|
||||||
VER_INFO="当前版本: \`v${MASTER_VERSION}\`"
|
|
||||||
|
|
||||||
BTN_MASTER_OTA=""
|
|
||||||
if [ -n "$REMOTE_VER" ] && [ "$REMOTE_VER" != "$MASTER_VERSION" ]; then
|
|
||||||
VER_INFO="${VER_INFO}\n✨ **发现新版本**: \`v${REMOTE_VER}\` (可执行中枢热重载)"
|
|
||||||
|
|
||||||
# 仅当非官方网关 且 开启了中枢 OTA 权限时,才渲染升级按钮
|
|
||||||
if [ "$IS_OFFICIAL_GATEWAY" != "true" ] && [ "${ENABLE_MASTER_OTA:-false}" == "true" ]; then
|
|
||||||
BTN_MASTER_OTA="[{\"text\":\"🆙 升级司令部至 v${REMOTE_VER}\",\"callback_data\":\"master_ota_confirm\"}],"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
NODE_COUNT=$(db_exec "SELECT COUNT(*) FROM nodes WHERE chat_id='$CHAT_ID';")
|
|
||||||
[ -z "$NODE_COUNT" ] && NODE_COUNT=0
|
|
||||||
|
|
||||||
# L0 扁平化重构:将司令部升级按钮动态置于最顶层
|
|
||||||
if [ "$IS_OFFICIAL_GATEWAY" != "true" ]; then
|
|
||||||
BTNS="[${BTN_MASTER_OTA}[{\"text\":\"🌍 进入全球战区雷达 (管理节点)\",\"callback_data\":\"list_nodes\"}], [{\"text\":\"🚀 全军总攻\",\"callback_data\":\"all_run\"}, {\"text\":\"📊 全军简报\",\"callback_data\":\"all_reports\"}], [{\"text\":\"☢️ 全舰队 OTA 热重载\",\"callback_data\":\"all_ota_confirm\"}]]"
|
|
||||||
else
|
|
||||||
BTNS="[[{\"text\":\"🌍 进入全球战区雷达 (管理节点)\",\"callback_data\":\"list_nodes\"}], [{\"text\":\"🚀 全军总攻\",\"callback_data\":\"all_run\"}, {\"text\":\"📊 全军简报\",\"callback_data\":\"all_reports\"}]]"
|
|
||||||
fi
|
|
||||||
TEXT_MSG="🛡️ **IP-Sentinel 司令部**\n${VER_INFO}\n\n📊 舰队状态: 共有 \`${NODE_COUNT}\` 台哨兵在线\n欢迎回来,长官。请下达战略指令:"
|
|
||||||
send_ui "$CHAT_ID" "$TEXT_MSG" "$BTNS"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"all_ota_confirm")
|
|
||||||
CONFIRM_BTNS="[[{\"text\":\"🚨 我已了解风险,下发核按钮指令!\",\"callback_data\":\"all_ota_execute\"}], [{\"text\":\"取消操作\",\"callback_data\":\"/start\"}]]"
|
|
||||||
WARNING_MSG="☢️ **【最高指令:全舰队 OTA 升级】**\n\n此操作将向您名下**所有开启 OTA 权限的节点**下发重组指令,强制从云端拉取最新代码并进行热重载。\n\n⚠️ **核按钮风险提示**:\n1. 升级过程中守护进程会短暂重启,节点可能出现临时离线。\n2. 若遇 GitHub 源屏蔽或网络极度恶劣,少数节点可能需要手动干预。\n\n**是否确定挂载并执行 OTA 指令?**"
|
|
||||||
send_ui "$CHAT_ID" "$WARNING_MSG" "$CONFIRM_BTNS"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"all_ota_execute")
|
|
||||||
NODE_DATA=$(db_exec "SELECT node_name, agent_ip, agent_port FROM nodes WHERE chat_id='$CHAT_ID' AND enable_ota='true';")
|
|
||||||
if [ -z "$NODE_DATA" ]; then
|
|
||||||
send_msg "$CHAT_ID" "⚠️ 您名下暂无开启 OTA 权限的在线节点。"
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在唤醒全舰队执行 OTA 升级...**%0A*(节点升级成功后会主动发回新的入库确认,请注意查收)*"
|
|
||||||
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
|
|
||||||
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_ota")
|
|
||||||
{ curl -k -s -m 5 "$TARGET_URL" || curl -s -m 5 "${TARGET_URL/https:\/\//http:\/\/}"; } > /dev/null &
|
|
||||||
sleep 0.3 # 严格流量削峰
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
"master_ota_confirm")
|
|
||||||
CONFIRM_BTNS="[[{\"text\":\"🚨 确认重构司令部\",\"callback_data\":\"master_ota_execute\"}], [{\"text\":\"取消操作\",\"callback_data\":\"/start\"}]]"
|
|
||||||
WARNING_MSG="☢️ **【最高指令:中枢金蝉脱壳】**\n\n此操作将拉取最新源码并强行覆盖司令部核心进程。\n\n⚠️ **风险提示**:\n升级期间司令部将短暂失联(约3-5秒)。完成后会自动发送捷报。\n\n**是否确定执行司令部自我升级?**"
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_ui "$CHAT_ID" "$MSG_ID" "$WARNING_MSG" "$CONFIRM_BTNS"
|
|
||||||
else
|
|
||||||
send_ui "$CHAT_ID" "$WARNING_MSG" "$CONFIRM_BTNS"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
"master_ota_execute")
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_msg "$CHAT_ID" "$MSG_ID" "⏳ 正在下载重构图纸,司令部即将进入静默重启..."
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "⏳ 正在下载重构图纸,司令部即将进入静默重启..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 下载最新的 master install 脚本作为幽灵进程
|
|
||||||
curl -fsSL "${REPO_RAW_URL}/master/install_master.sh" -o "/tmp/install_master.sh"
|
|
||||||
|
|
||||||
# [v3.6.3 修复] 🚀 OTA 防砖机制:严格校验脚本完整性
|
|
||||||
if ! bash -n "/tmp/install_master.sh" >/dev/null 2>&1; then
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_msg "$CHAT_ID" "$MSG_ID" "❌ OTA 传输受损:脚本下载不完整,已触发防砖熔断,升级取消!"
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "❌ OTA 传输受损:脚本下载不完整,已触发防砖熔断,升级取消!"
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod +x "/tmp/install_master.sh"
|
|
||||||
|
|
||||||
# 抛出幽灵进程进行脱壳升级,传递静默变量与回执 ID
|
|
||||||
# [修复] 必须显式将环境变量注入到 bash -c 的指令串中,防止被 systemd-run 沙盒隔离丢弃
|
|
||||||
if command -v systemd-run >/dev/null 2>&1; then
|
|
||||||
systemd-run --quiet --no-block /bin/bash -c "export SILENT_MASTER_OTA='true'; export OTA_CHAT_ID='$CHAT_ID'; bash /tmp/install_master.sh"
|
|
||||||
else
|
|
||||||
export SILENT_MASTER_OTA="true"
|
|
||||||
export OTA_CHAT_ID="$CHAT_ID"
|
|
||||||
nohup bash /tmp/install_master.sh >/dev/null 2>&1 & disown
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 当前旧进程休眠并等待被幽灵进程处决
|
|
||||||
sleep 10
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
"all_reports")
|
"all_reports")
|
||||||
@@ -299,13 +154,11 @@ while true; do
|
|||||||
if [ -z "$NODE_DATA" ]; then
|
if [ -z "$NODE_DATA" ]; then
|
||||||
send_msg "$CHAT_ID" "⚠️ 您名下暂无在线节点。"
|
send_msg "$CHAT_ID" "⚠️ 您名下暂无在线节点。"
|
||||||
else
|
else
|
||||||
# [文案优化] 提前告知指挥官需要排队等待
|
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在召唤所有哨兵回传简报...**"
|
||||||
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在召唤所有哨兵回传简报...**%0A*(为防止触发 TG 官方限流,简报将排队依次送达,请耐心等待)*"
|
|
||||||
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
|
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
|
||||||
|
# 🛡️ [v3.0.4] 动态签名防重放批量下发
|
||||||
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_report")
|
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_report")
|
||||||
{ curl -k -s -m 5 "$TARGET_URL" || curl -s -m 5 "${TARGET_URL/https:\/\//http:\/\/}"; } > /dev/null &
|
curl -s -m 5 "$TARGET_URL" > /dev/null &
|
||||||
# [致命修复] 强行休眠 2 秒!错开 TG 官方 1条/秒 的发信红线
|
|
||||||
sleep 2
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -318,9 +171,9 @@ while true; do
|
|||||||
else
|
else
|
||||||
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在唤醒所有哨兵执行系统维护...**"
|
send_msg "$CHAT_ID" "📢 **司令部指令下达:正在唤醒所有哨兵执行系统维护...**"
|
||||||
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
|
echo "$NODE_DATA" | while IFS='|' read -r NNAME AIP APORT; do
|
||||||
|
# 🛡️ [v3.0.4] 动态签名防重放批量下发 (维护模块)
|
||||||
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_run")
|
TARGET_URL=$(generate_signed_url "$AIP" "$APORT" "/trigger_run")
|
||||||
curl -k -s -m 5 "$TARGET_URL" > /dev/null &
|
curl -s -m 5 "$TARGET_URL" > /dev/null &
|
||||||
sleep 0.2 # [新增] 流量削峰:防止瞬间 fork 导致句柄耗尽
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -339,14 +192,11 @@ 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"
|
||||||
# L1 追加返回中枢逃生舱
|
BTNS="${BTNS%,}]"
|
||||||
BTNS="$BTNS[{\"text\":\"🏠 回到司令部\",\"callback_data\":\"/start\"}]]"
|
send_ui "$CHAT_ID" "🌍 **全视界战略雷达**\n请选择要检阅的战区:" "$BTNS"
|
||||||
send_ui "$CHAT_ID" "🌍 **全视界战略雷达**\n已为您聚合当前舰队的部署大区,请选择要检阅的战区:" "$BTNS"
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
@@ -355,17 +205,15 @@ 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-')
|
||||||
|
|
||||||
# [v3.5.2] 提取物理主键和展示别名
|
NODE_LIST=$(db_exec "SELECT node_name FROM nodes WHERE chat_id='$CHAT_ID' AND region='$TARGET_REGION';")
|
||||||
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="["
|
||||||
while IFS='|' read -r N_NAME N_ALIAS; do
|
for N in $NODE_LIST; do
|
||||||
[ -z "$N_NAME" ] && continue
|
ROW_STR="$ROW_STR{\"text\":\"🖥️ $N\",\"callback_data\":\"manage:$N\"},"
|
||||||
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%,}]"
|
||||||
@@ -373,123 +221,24 @@ while true; do
|
|||||||
COL=0
|
COL=0
|
||||||
ROW_STR="["
|
ROW_STR="["
|
||||||
fi
|
fi
|
||||||
done <<< "$NODE_LIST"
|
done
|
||||||
# 如果是奇数,补齐最后的尾巴
|
# 如果是奇数,补齐最后的尾巴
|
||||||
if [ $COL -eq 1 ]; then
|
if [ $COL -eq 1 ]; then
|
||||||
ROW_STR="${ROW_STR%,}]"
|
ROW_STR="${ROW_STR%,}]"
|
||||||
BTNS="$BTNS$ROW_STR,"
|
BTNS="$BTNS$ROW_STR,"
|
||||||
fi
|
fi
|
||||||
# L2 追加双重逃生舱
|
# 添加返回上级大区雷达的按钮
|
||||||
BTNS="$BTNS[{\"text\":\"⬅️ 返回战区地图\",\"callback_data\":\"list_nodes\"}, {\"text\":\"🏠 回到司令部\",\"callback_data\":\"/start\"}]]"
|
BTNS="$BTNS[{\"text\":\"⬅️ 返回全球战区分布\",\"callback_data\":\"list_nodes\"}]]"
|
||||||
send_ui "$CHAT_ID" "📍 **[$TARGET_REGION] 战区哨兵矩阵**\n请锁定要执行战术动作的具体目标:" "$BTNS"
|
send_ui "$CHAT_ID" "📍 **[$TARGET_REGION] 战区哨兵矩阵**\n请下达控制指令:" "$BTNS"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
manage:*)
|
manage:*)
|
||||||
|
# 🛡️ 强制过滤节点名,防止面板渲染时发生 XSS 或注入
|
||||||
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
|
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;")
|
# 【核心升级】拆分下发按钮,精准对应 Google 与 Trust 两个模块,并排版为 3 行 2 列
|
||||||
[ -z "$TARGET_ALIAS" ] && TARGET_ALIAS="$TARGET_NODE"
|
BTNS="[[{\"text\":\"📍 Google 纠偏\",\"callback_data\":\"google:$TARGET_NODE\"}, {\"text\":\"🛡️ 信用净化\",\"callback_data\":\"trust:$TARGET_NODE\"}], [{\"text\":\"📜 实时日志\",\"callback_data\":\"log:$TARGET_NODE\"}, {\"text\":\"📊 统计战报\",\"callback_data\":\"report:$TARGET_NODE\"}], [{\"text\":\"🗑️ 剔除失联节点\",\"callback_data\":\"del:$TARGET_NODE\"}, {\"text\":\"⬅️ 返回大区目录\",\"callback_data\":\"list_nodes\"}]]"
|
||||||
|
send_ui "$CHAT_ID" "⚙️ **目标锁定**: \`$TARGET_NODE\`\n请选择战术动作:" "$BTNS"
|
||||||
# 抓取节点全景元数据
|
|
||||||
TOGGLE_INFO=$(db_exec "SELECT enable_google, enable_trust, enable_ota, agent_ip, IFNULL(last_seen, '未知') 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_OTA=$(echo "$TOGGLE_INFO" | cut -d'|' -f3)
|
|
||||||
A_IP=$(echo "$TOGGLE_INFO" | cut -d'|' -f4)
|
|
||||||
LAST_SEEN=$(echo "$TOGGLE_INFO" | cut -d'|' -f5)
|
|
||||||
|
|
||||||
# 动态渲染状态文字
|
|
||||||
[ "$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"; }
|
|
||||||
|
|
||||||
# 模块一:即时战术动作
|
|
||||||
BTN_ACTION="[{\"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\"}]"
|
|
||||||
|
|
||||||
# 模块二:养护状态启停
|
|
||||||
BTN_TOGGLE="[{\"text\":\"$BTN_G\",\"callback_data\":\"toggle:google:$TARGET_NODE:$ACT_G\"}, {\"text\":\"$BTN_T\",\"callback_data\":\"toggle:trust:$TARGET_NODE:$ACT_T\"}]"
|
|
||||||
|
|
||||||
# 模块三:深度配置管理 (结合 UI 熔断)
|
|
||||||
if [ "$IS_OFFICIAL_GATEWAY" != "true" ] && [ "$ST_OTA" == "true" ]; then
|
|
||||||
BTN_CONFIG="[{\"text\":\"✏️ 更改终端展示代号\",\"callback_data\":\"rename:$TARGET_NODE\"}, {\"text\":\"🆙 OTA 静默升级\",\"callback_data\":\"ota_confirm:$TARGET_NODE\"}]"
|
|
||||||
else
|
|
||||||
BTN_CONFIG="[{\"text\":\"✏️ 更改终端展示代号\",\"callback_data\":\"rename:$TARGET_NODE\"}]"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 模块四:危险区与逃生舱
|
|
||||||
BTN_DANGER="[{\"text\":\"🗑️ 从中枢销毁该档案\",\"callback_data\":\"del:$TARGET_NODE\"}, {\"text\":\"⬅️ 返回战区列表\",\"callback_data\":\"list_nodes\"}]"
|
|
||||||
|
|
||||||
# 组合终极矩阵
|
|
||||||
BTNS="[$BTN_ACTION, $BTN_TOGGLE, $BTN_CONFIG, $BTN_DANGER]"
|
|
||||||
|
|
||||||
TEXT_MSG="⚙️ **目标锁定**: \`$TARGET_ALIAS\`\n(底层标识: \`$TARGET_NODE\`)\n🌐 IP 坐标: \`$A_IP\`\n🕒 最后通讯: \`$LAST_SEEN\`\n\n请下达精确控制指令:"
|
|
||||||
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_ui "$CHAT_ID" "$MSG_ID" "$TEXT_MSG" "$BTNS"
|
|
||||||
else
|
|
||||||
send_ui "$CHAT_ID" "$TEXT_MSG" "$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 -k -s -m 5 "$TARGET_URL" || echo "FAILED")
|
|
||||||
# [向下兼容补丁] 若 HTTPS 拒绝或超时,回退 HTTP 试探老节点
|
|
||||||
if [ "$RESPONSE" == "FAILED" ] || [ -z "$RESPONSE" ]; then
|
|
||||||
TARGET_URL_HTTP="${TARGET_URL/https:\/\//http:\/\/}"
|
|
||||||
RESPONSE=$(curl -s -m 5 "$TARGET_URL_HTTP" || echo "FAILED")
|
|
||||||
fi
|
|
||||||
|
|
||||||
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"; }
|
|
||||||
|
|
||||||
# 切换后直接复用扁平化 L3 面板的重绘逻辑
|
|
||||||
TOGGLE_INFO=$(db_exec "SELECT enable_google, enable_trust, enable_ota, agent_ip, IFNULL(last_seen, '未知') 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_OTA=$(echo "$TOGGLE_INFO" | cut -d'|' -f3)
|
|
||||||
A_IP=$(echo "$TOGGLE_INFO" | cut -d'|' -f4)
|
|
||||||
LAST_SEEN=$(echo "$TOGGLE_INFO" | cut -d'|' -f5)
|
|
||||||
|
|
||||||
[ "$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"; }
|
|
||||||
|
|
||||||
BTN_ACTION="[{\"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\"}]"
|
|
||||||
BTN_TOGGLE="[{\"text\":\"$BTN_G\",\"callback_data\":\"toggle:google:$TARGET_NODE:$ACT_G\"}, {\"text\":\"$BTN_T\",\"callback_data\":\"toggle:trust:$TARGET_NODE:$ACT_T\"}]"
|
|
||||||
|
|
||||||
if [ "$IS_OFFICIAL_GATEWAY" != "true" ] && [ "$ST_OTA" == "true" ]; then
|
|
||||||
BTN_CONFIG="[{\"text\":\"✏️ 更改终端展示代号\",\"callback_data\":\"rename:$TARGET_NODE\"}, {\"text\":\"🆙 OTA 静默升级\",\"callback_data\":\"ota_confirm:$TARGET_NODE\"}]"
|
|
||||||
else
|
|
||||||
BTN_CONFIG="[{\"text\":\"✏️ 更改终端展示代号\",\"callback_data\":\"rename:$TARGET_NODE\"}]"
|
|
||||||
fi
|
|
||||||
BTN_DANGER="[{\"text\":\"🗑️ 从中枢销毁该档案\",\"callback_data\":\"del:$TARGET_NODE\"}, {\"text\":\"⬅️ 返回战区列表\",\"callback_data\":\"list_nodes\"}]"
|
|
||||||
|
|
||||||
BTNS="[$BTN_ACTION, $BTN_TOGGLE, $BTN_CONFIG, $BTN_DANGER]"
|
|
||||||
TARGET_ALIAS=$(db_exec "SELECT IFNULL(node_alias, node_name) FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;")
|
|
||||||
|
|
||||||
TEXT_MSG="⚙️ **目标锁定**: \`$TARGET_ALIAS\`\n(底层标识: \`$TARGET_NODE\`)\n🌐 IP 坐标: \`$A_IP\`\n🕒 最后通讯: \`$LAST_SEEN\`\n\n✅ **执行成功**: 模块 [$MOD_NAME] 状态已切换为 $TARGET_STATE!"
|
|
||||||
edit_ui "$CHAT_ID" "$MSG_ID" "$TEXT_MSG" "$BTNS"
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "❌ 指令下发失败,节点可能离线或未更新至 v3.5.3。"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
del:*)
|
del:*)
|
||||||
@@ -512,8 +261,6 @@ 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"
|
||||||
@@ -522,103 +269,6 @@ 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 -k -s -m 5 "$TARGET_URL" || echo "FAILED")
|
|
||||||
# [向下兼容补丁] 若 HTTPS 拒绝或超时,回退 HTTP 试探老节点
|
|
||||||
if [ "$RESPONSE" == "FAILED" ] || [ -z "$RESPONSE" ]; then
|
|
||||||
TARGET_URL_HTTP="${TARGET_URL/https:\/\//http:\/\/}"
|
|
||||||
RESPONSE=$(curl -s -m 5 "$TARGET_URL_HTTP" || echo "FAILED")
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
;;
|
|
||||||
|
|
||||||
ota_confirm:*)
|
|
||||||
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
|
|
||||||
# 将取消动作引导回 manage,因为 adv 已经被删除了
|
|
||||||
CONFIRM_BTNS="[[{\"text\":\"🚨 确认执行远程升级\",\"callback_data\":\"ota_execute:$TARGET_NODE\"}], [{\"text\":\"取消\",\"callback_data\":\"manage:$TARGET_NODE\"}]]"
|
|
||||||
send_ui "$CHAT_ID" "☢️ **操作确认**:即将向 \`$TARGET_NODE\` 下发 OTA 热更新指令。\n节点更新完成后会自动发送包含新版本号的注册回执,确定执行?" "$CONFIRM_BTNS"
|
|
||||||
;;
|
|
||||||
|
|
||||||
ota_execute:*)
|
|
||||||
TARGET_NODE=$(echo "${TEXT#*:}" | tr -cd 'a-zA-Z0-9_.-')
|
|
||||||
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
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_msg "$CHAT_ID" "$MSG_ID" "⏳ 正在向 \`$TARGET_NODE\` 发送 OTA 触发报文..."
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "⏳ 正在向 \`$TARGET_NODE\` 发送 OTA 触发报文..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARGET_URL=$(generate_signed_url "$AGENT_IP" "$AGENT_PORT" "/trigger_ota")
|
|
||||||
RESPONSE=$(curl -k -s -m 5 "$TARGET_URL" || echo "FAILED")
|
|
||||||
# [向下兼容补丁] 若 HTTPS 拒绝或超时,回退 HTTP 试探老节点
|
|
||||||
if [ "$RESPONSE" == "FAILED" ] || [ -z "$RESPONSE" ]; then
|
|
||||||
TARGET_URL_HTTP="${TARGET_URL/https:\/\//http:\/\/}"
|
|
||||||
RESPONSE=$(curl -s -m 5 "$TARGET_URL_HTTP" || echo "FAILED")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$RESPONSE" == "FAILED" ]; then
|
|
||||||
TEXT_RES="❌ OTA 指令下发超时!请检查节点公网连通性。"
|
|
||||||
elif [[ "$RESPONSE" == *"403"* ]]; then
|
|
||||||
TEXT_RES="⚠️ **节点拒绝执行**:该节点本地未开启 OTA 权限或运行在官方网关下!"
|
|
||||||
else
|
|
||||||
TEXT_RES="✅ OTA 触发成功!节点正在后台执行拉取重构,请等待其发送更新完成的回执消息。"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$MSG_ID" ]; then
|
|
||||||
edit_msg "$CHAT_ID" "$MSG_ID" "$TEXT_RES"
|
|
||||||
else
|
|
||||||
send_msg "$CHAT_ID" "$TEXT_RES"
|
|
||||||
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
|
||||||
@@ -640,12 +290,7 @@ while true; do
|
|||||||
|
|
||||||
# 🛡️ [v3.0.4] 动态签名生成与触发 (防重放与防篡改)
|
# 🛡️ [v3.0.4] 动态签名生成与触发 (防重放与防篡改)
|
||||||
TARGET_URL=$(generate_signed_url "$AGENT_IP" "$AGENT_PORT" "/trigger_${ACTION_TYPE}")
|
TARGET_URL=$(generate_signed_url "$AGENT_IP" "$AGENT_PORT" "/trigger_${ACTION_TYPE}")
|
||||||
RESPONSE=$(curl -k -s -m 5 "$TARGET_URL" || echo "FAILED")
|
RESPONSE=$(curl -s -m 5 "$TARGET_URL" || echo "FAILED")
|
||||||
# [向下兼容补丁] 若 HTTPS 拒绝或超时,回退 HTTP 试探老节点
|
|
||||||
if [ "$RESPONSE" == "FAILED" ] || [ -z "$RESPONSE" ]; then
|
|
||||||
TARGET_URL_HTTP="${TARGET_URL/https:\/\//http:\/\/}"
|
|
||||||
RESPONSE=$(curl -s -m 5 "$TARGET_URL_HTTP" || echo "FAILED")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 结果判定
|
# 结果判定
|
||||||
if [ "$RESPONSE" == "FAILED" ]; then
|
if [ "$RESPONSE" == "FAILED" ]; then
|
||||||
|
|||||||
@@ -1,30 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本 - 动态锚点版)
|
# 脚本名称: uninstall_master.sh (IP-Sentinel Master 一键卸载脚本)
|
||||||
# 核心功能: 终止调度进程、清理看门狗定时任务、抹除数据库与配置
|
# 核心功能: 终止调度进程、清理看门狗定时任务、抹除数据库与配置
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 🛑 核心权限防线: 检查是否以 root 权限运行
|
|
||||||
# ==========================================================
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
echo -e "\033[31m❌ 权限被拒绝: 卸载 IP-Sentinel 需要最高系统权限。\033[0m"
|
|
||||||
echo -e "💡 请切换到 root 用户 (执行 su root 或 sudo -i) 后重新运行指令。"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
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 数据库!"
|
||||||
@@ -34,30 +18,18 @@ if [[ ! "$CONFIRM_DEL" =~ ^[Yy]$ ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 1. 停止并删除 Systemd 服务 (适配新架构)
|
# 1. 停止运行中的 Master 守护进程
|
||||||
echo "[1/4] 正在停止并删除 Systemd 服务..."
|
echo "[1/3] 正在终止后台中枢调度进程..."
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
pgrep -f tg_master.sh | xargs -r kill -9 >/dev/null 2>&1
|
||||||
echo "💡 检测到 Systemd 环境,正在抹除 Systemd 服务单元..."
|
|
||||||
systemctl disable --now ip-sentinel-master.service >/dev/null 2>&1
|
|
||||||
rm -f /etc/systemd/system/ip-sentinel-master.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl reset-failed
|
|
||||||
else
|
|
||||||
echo "💡 未检测到 Systemd,跳过此步骤..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. 停止运行中的 Master 守护进程 (兜底清理老版进程)
|
# 2. 清除看门狗定时任务 (Cron)
|
||||||
echo "[2/4] 正在终止后台中枢调度进程..."
|
echo "[2/3] 正在清理系统定时任务 (Cron)..."
|
||||||
pkill -9 -f "tg_master.sh" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# 3. 清除看门狗定时任务 (Cron)
|
|
||||||
echo "[3/4] 正在清理系统定时任务 (Cron)..."
|
|
||||||
crontab -l 2>/dev/null | grep -v "tg_master.sh" > /tmp/cron_backup
|
crontab -l 2>/dev/null | grep -v "tg_master.sh" > /tmp/cron_backup
|
||||||
crontab /tmp/cron_backup
|
crontab /tmp/cron_backup
|
||||||
rm -f /tmp/cron_backup
|
rm -f /tmp/cron_backup
|
||||||
|
|
||||||
# 4. 删除所有文件、配置与数据库
|
# 3. 删除所有文件、配置与数据库
|
||||||
echo "[4/4] 正在抹除核心程序、配置文件与 SQLite 数据库..."
|
echo "[3/3] 正在抹除核心程序、配置文件与 SQLite 数据库..."
|
||||||
if [ -d "$MASTER_DIR" ]; then
|
if [ -d "$MASTER_DIR" ]; then
|
||||||
rm -rf "$MASTER_DIR"
|
rm -rf "$MASTER_DIR"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
import urllib.request
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
# ================== [路径防弹装甲] ==================
|
|
||||||
# 无论在哪里执行该脚本,都能精准反推项目根目录
|
|
||||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
|
|
||||||
|
|
||||||
MAP_JSON_PATH = os.path.join(PROJECT_ROOT, "data", "map.json")
|
|
||||||
DATA_DIR = os.path.join(PROJECT_ROOT, "data", "keywords")
|
|
||||||
# ====================================================
|
|
||||||
|
|
||||||
# 特殊战区代码映射 (Google Trends RSS 要求)
|
|
||||||
GEO_FIX = {'UK': 'GB'}
|
|
||||||
|
|
||||||
HEADERS = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_active_regions():
|
|
||||||
"""动态提取 map.json 中的战区 (适配 v3.5.0 大洲战区降维架构)"""
|
|
||||||
try:
|
|
||||||
with open(MAP_JSON_PATH, 'r', encoding='utf-8') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
regions = []
|
|
||||||
# 第一层穿透:遍历所有大洲战区 (continents)
|
|
||||||
for continent in data.get('continents', []):
|
|
||||||
# 第二层穿透:遍历大洲下的所有国家 (countries)
|
|
||||||
for country in continent.get('countries', []):
|
|
||||||
if 'id' in country:
|
|
||||||
regions.append(country['id'])
|
|
||||||
return regions
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ [读取地图失败]: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def fetch_trends(region_code):
|
|
||||||
"""从 Google Trends 抓取当日热搜"""
|
|
||||||
geo = GEO_FIX.get(region_code, region_code)
|
|
||||||
url = f"https://trends.google.com/trending/rss?geo={geo}"
|
|
||||||
try:
|
|
||||||
req = urllib.request.Request(url, headers=HEADERS)
|
|
||||||
with urllib.request.urlopen(req, timeout=10) as response:
|
|
||||||
xml_data = response.read()
|
|
||||||
root = ET.fromstring(xml_data)
|
|
||||||
return [re.sub(r'[\n\r\t]', ' ', item.find('title').text).strip()
|
|
||||||
for item in root.findall('./channel/item')
|
|
||||||
if item.find('title') is not None]
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ {region_code} 抓取异常: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def update_file(region, new_words):
|
|
||||||
"""滑动窗口更新,保留 200 条最热记录"""
|
|
||||||
os.makedirs(DATA_DIR, exist_ok=True)
|
|
||||||
file_path = os.path.join(DATA_DIR, f"kw_{region}.txt")
|
|
||||||
old_words = []
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
old_words = [l.strip() for l in f if l.strip()]
|
|
||||||
|
|
||||||
# 新词排在最前面,去重
|
|
||||||
combined = new_words + [w for w in old_words if w not in new_words]
|
|
||||||
final_list = combined[:200]
|
|
||||||
|
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write('\n'.join(final_list) + '\n')
|
|
||||||
print(f"✅ [同步完成] {region}: 注入 {len(new_words)} 条新热点")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
regions = get_active_regions()
|
|
||||||
if not regions:
|
|
||||||
print("🛑 未发现活跃战区,请检查 map.json")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
for r in regions:
|
|
||||||
print(f"📡 正在拉取 {r} 战区情报...")
|
|
||||||
words = fetch_trends(r)
|
|
||||||
if words:
|
|
||||||
update_file(r, words)
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
MASTER_VERSION=3.6.3
|
|
||||||
AGENT_VERSION=3.6.3
|
|
||||||
Reference in New Issue
Block a user