diff --git a/README.md b/README.md index 2100db4..2d54a0b 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,6 @@ sub-provider/ rules.py subscriptions.py config/ - app.yaml - clients.yaml - policy-groups.yaml - regions.yaml - rules.yaml sources.yaml rules/ reject.yaml @@ -78,22 +73,13 @@ cp .env.example .env - 也可以直接填本地文件路径,例如 `/app/data/sources/airport-b.txt` 或 `file:///app/data/sources/airport-b.txt` - 允许把其中一个留空;留空时这个机场会自动跳过 -3. 按需调整 `config/` 下拆分后的配置文件: - -- `app.yaml`:公共访问路径 -- `sources.yaml`:机场源配置 -- `regions.yaml`:地区自动组 -- `policy-groups.yaml`:选择器组和业务策略组 -- `rules.yaml`:规则绑定 -- `clients.yaml`:客户端模板 - -4. 启动: +3. 启动: ```bash docker compose up -d --build ``` -5. 访问检查: +4. 访问检查: - 健康检查:`http://YOUR_HOST:18080/healthz` - 单 provider: diff --git a/app/config.py b/app/config.py index ecb0087..5ef33b6 100644 --- a/app/config.py +++ b/app/config.py @@ -27,13 +27,7 @@ class Settings(BaseSettings): max_proxy_name_length: int = 80 default_user_agent: str = "sub-provider/0.2" - config_dir: Path = CONFIG_DIR sources_file: Path = CONFIG_DIR / "sources.yaml" - app_config_file: Path = CONFIG_DIR / "app.yaml" - clients_file: Path = CONFIG_DIR / "clients.yaml" - regions_file: Path = CONFIG_DIR / "regions.yaml" - policy_groups_file: Path = CONFIG_DIR / "policy-groups.yaml" - rules_config_file: Path = CONFIG_DIR / "rules.yaml" rules_dir: Path = CONFIG_DIR / "rules" bundle_cache_dir: Path = ROOT_DIR / "output" / "bundle-cache" fetch_cache_dir: Path = DATA_DIR / "fetch-cache" diff --git a/app/services/loader.py b/app/services/loader.py index edac2f5..dd12bbd 100644 --- a/app/services/loader.py +++ b/app/services/loader.py @@ -3,7 +3,6 @@ from __future__ import annotations import os import re from pathlib import Path -from typing import Any import yaml @@ -22,44 +21,7 @@ def _expand_env(value): return value -def _load_yaml(path: Path) -> dict[str, Any]: - if not path.is_file(): - return {} - raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {} - if not isinstance(raw, dict): - raise ValueError(f"Config file must contain a top-level mapping: {path}") - return raw - - -def _section_value(raw: dict[str, Any], section: str) -> Any: - if section in raw: - return raw[section] - return raw - - -def _load_split_config(path: Path) -> dict[str, Any]: - config_dir = path if path.is_dir() else path.parent - legacy_raw = _load_yaml(path) if path.is_file() else {} - - app_raw = _load_yaml(config_dir / "app.yaml") - sources_raw = _load_yaml(config_dir / "sources.yaml") - regions_raw = _load_yaml(config_dir / "regions.yaml") - groups_raw = _load_yaml(config_dir / "policy-groups.yaml") - rules_raw = _load_yaml(config_dir / "rules.yaml") - clients_raw = _load_yaml(config_dir / "clients.yaml") - - merged: dict[str, Any] = {} - merged.update({key: value for key, value in legacy_raw.items() if key not in AppConfig.model_fields}) - merged["public_path"] = app_raw.get("public_path", legacy_raw.get("public_path")) - merged["sources"] = _section_value(sources_raw, "sources") if sources_raw else legacy_raw.get("sources", {}) - merged["regions"] = _section_value(regions_raw, "regions") if regions_raw else legacy_raw.get("regions", {}) - merged["selector_groups"] = groups_raw.get("selector_groups", legacy_raw.get("selector_groups", [])) - merged["policy_groups"] = groups_raw.get("policy_groups", legacy_raw.get("policy_groups", [])) - merged["rules"] = _section_value(rules_raw, "rules") if rules_raw else legacy_raw.get("rules", {}) - merged["clients"] = _section_value(clients_raw, "clients") if clients_raw else legacy_raw.get("clients", {}) - return merged - - def load_app_config(path: Path) -> AppConfig: - expanded = _expand_env(_load_split_config(path)) + raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + expanded = _expand_env(raw) return AppConfig.model_validate(expanded) diff --git a/config/app.yaml b/config/app.yaml deleted file mode 100644 index 0a45c10..0000000 --- a/config/app.yaml +++ /dev/null @@ -1 +0,0 @@ -public_path: ${PUBLIC_PATH} diff --git a/config/clients.yaml b/config/clients.yaml deleted file mode 100644 index 899fce0..0000000 --- a/config/clients.yaml +++ /dev/null @@ -1,33 +0,0 @@ -clients: - mihomo: - title: HomeLab Mihomo - provider_interval: 21600 - rule_interval: 86400 - test_url: https://www.gstatic.com/generate_204 - test_interval: 300 - main_policy: 🚀 节点选择 - source_policy: ☁️ 机场选择 - mixed_auto_policy: ♻️ 自动选择 - manual_policy: 🚀 手动切换 - direct_policy: DIRECT - mode: rule - allow_lan: true - ipv6: true - mixed_port: 7890 - socks_port: 7891 - log_level: info - - stash: - title: HomeLab Stash - provider_interval: 21600 - rule_interval: 86400 - test_url: https://www.gstatic.com/generate_204 - test_interval: 300 - main_policy: 🚀 节点选择 - source_policy: ☁️ 机场选择 - mixed_auto_policy: ♻️ 自动选择 - manual_policy: 🚀 手动切换 - direct_policy: DIRECT - mode: rule - ipv6: true - log_level: info diff --git a/config/policy-groups.yaml b/config/policy-groups.yaml deleted file mode 100644 index b74f532..0000000 --- a/config/policy-groups.yaml +++ /dev/null @@ -1,180 +0,0 @@ -selector_groups: - - name: "🎥 奈飞节点" - type: select - filter: "(?i)(nf|奈飞|解锁|netflix|media)" - -policy_groups: - - name: "📲 电报消息" - type: select - proxies: - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "🇸🇬 狮城节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇯🇵 日本节点" - - "🇺🇲 美国节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - "{{ direct_policy }}" - - name: "💬 Ai平台" - type: select - proxies: - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "🇺🇲 美国节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - "{{ direct_policy }}" - - name: "📹 油管视频" - type: select - proxies: - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "🇸🇬 狮城节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇯🇵 日本节点" - - "🇺🇲 美国节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - "{{ direct_policy }}" - - name: "🎥 奈飞视频" - type: select - proxies: - - "🎥 奈飞节点" - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "🇸🇬 狮城节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇯🇵 日本节点" - - "🇺🇲 美国节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - "{{ direct_policy }}" - - name: "🌍 国外媒体" - type: select - proxies: - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇺🇲 美国节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - "{{ direct_policy }}" - - name: "📢 谷歌" - type: select - proxies: - - "{{ main_policy }}" - - "{{ direct_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "Ⓜ️ 微软Bing" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "Ⓜ️ 微软云盘" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "Ⓜ️ 微软服务" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "🍎 苹果服务" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "🎮 游戏平台" - type: select - proxies: - - "{{ main_policy }}" - - "{{ direct_policy }}" - - "🇺🇲 美国节点" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "🎮 PT平台" - type: select - proxies: - - "{{ main_policy }}" - - "{{ direct_policy }}" - - "🇭🇰 香港节点" - - "🇨🇳 台湾节点" - - "🇸🇬 狮城节点" - - "🇯🇵 日本节点" - - "🇺🇲 美国节点" - - "🇰🇷 韩国节点" - - "{{ manual_policy }}" - - name: "🎯 全球直连" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - name: "🛑 广告拦截" - type: select - proxies: - - REJECT - - "{{ direct_policy }}" - - name: "🍃 应用净化" - type: select - proxies: - - REJECT - - "{{ direct_policy }}" - - name: "🐟 漏网之鱼" - type: select - proxies: - - "{{ direct_policy }}" - - "{{ main_policy }}" - - "{{ mixed_auto_policy }}" - - "{{ selector_groups }}" - - "{{ manual_policy }}" diff --git a/config/regions.yaml b/config/regions.yaml deleted file mode 100644 index b1fa742..0000000 --- a/config/regions.yaml +++ /dev/null @@ -1,25 +0,0 @@ -regions: - hk: - name: "🇭🇰 香港节点" - filter: "(?i)(港|hk|hong kong|hongkong)" - tolerance: 50 - tw: - name: "🇨🇳 台湾节点" - filter: "(?i)(台|新北|彰化|tw|taiwan)" - tolerance: 50 - sg: - name: "🇸🇬 狮城节点" - filter: "(?i)(新加坡|坡|狮城|sg|singapore)" - tolerance: 50 - jp: - name: "🇯🇵 日本节点" - filter: "(?i)(日本|东京|大阪|埼玉|jp|japan)" - tolerance: 50 - us: - name: "🇺🇲 美国节点" - filter: "(?i)(美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥|us|united states)" - tolerance: 150 - kr: - name: "🇰🇷 韩国节点" - filter: "(?i)(kr|korea|kor|首尔|韩|韓)" - tolerance: 50 diff --git a/config/rules.yaml b/config/rules.yaml deleted file mode 100644 index d628c13..0000000 --- a/config/rules.yaml +++ /dev/null @@ -1,210 +0,0 @@ -rules: - custom-proxy: - behavior: classical - format: text - policy: "{{ main_policy }}" - payload: - - DOMAIN-KEYWORD,cloudflare - - DOMAIN-KEYWORD,hetzner - - DOMAIN-KEYWORD,hdkylin - - DOMAIN-KEYWORD,steamusercontent - - DOMAIN-KEYWORD,steamcontent - - DOMAIN-KEYWORD,nintendo - - DOMAIN-KEYWORD,vscode - - DOMAIN-KEYWORD,btschool - - local-network: - file: acl4ssr/LocalAreaNetwork.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - unban: - file: acl4ssr/UnBan.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - reject: - file: acl4ssr/BanAD.yaml - behavior: classical - format: yaml - interval: 86400 - policy: "🛑 广告拦截" - - app-purify: - file: acl4ssr/BanProgramAD.yaml - behavior: classical - format: yaml - interval: 86400 - policy: "🍃 应用净化" - - google: - file: acl4ssr/Google.yaml - behavior: classical - format: yaml - policy: "📢 谷歌" - interval: 86400 - - google-cn: - file: acl4ssr/GoogleCN.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - steam-cn: - file: acl4ssr/SteamCN.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - microsoft-bing: - file: acl4ssr/Bing.yaml - behavior: classical - format: yaml - policy: "Ⓜ️ 微软Bing" - interval: 86400 - - microsoft-onedrive: - file: acl4ssr/OneDrive.yaml - behavior: classical - format: yaml - policy: "Ⓜ️ 微软云盘" - interval: 86400 - - microsoft: - file: acl4ssr/Microsoft.yaml - behavior: classical - format: yaml - policy: "Ⓜ️ 微软服务" - interval: 86400 - - apple: - file: acl4ssr/Apple.yaml - behavior: classical - format: yaml - policy: "🍎 苹果服务" - interval: 86400 - - telegram: - file: acl4ssr/Telegram.yaml - behavior: classical - format: yaml - policy: "📲 电报消息" - interval: 86400 - - ai: - file: acl4ssr/AI.yaml - behavior: classical - format: yaml - policy: "💬 Ai平台" - interval: 86400 - - openai: - file: acl4ssr/OpenAi.yaml - behavior: classical - format: yaml - policy: "💬 Ai平台" - interval: 86400 - - youtube: - file: acl4ssr/YouTube.yaml - behavior: classical - format: yaml - policy: "📹 油管视频" - interval: 86400 - - netflix: - file: acl4ssr/Netflix.yaml - behavior: classical - format: yaml - policy: "🎥 奈飞视频" - interval: 86400 - - proxy-media: - file: acl4ssr/ProxyMedia.yaml - behavior: classical - format: yaml - policy: "🌍 国外媒体" - interval: 86400 - - games-epic: - file: acl4ssr/Epic.yaml - behavior: classical - format: yaml - policy: "🎮 游戏平台" - interval: 86400 - - games-origin: - file: acl4ssr/Origin.yaml - behavior: classical - format: yaml - policy: "🎮 游戏平台" - interval: 86400 - - games-sony: - file: acl4ssr/Sony.yaml - behavior: classical - format: yaml - policy: "🎮 游戏平台" - interval: 86400 - - games-steam: - file: acl4ssr/Steam.yaml - behavior: classical - format: yaml - policy: "🎮 游戏平台" - interval: 86400 - - games-nintendo: - file: acl4ssr/Nintendo.yaml - behavior: classical - format: yaml - policy: "🎮 游戏平台" - interval: 86400 - - pt: - file: acl4ssr/PrivateTracker.yaml - behavior: classical - format: yaml - policy: "🎮 PT平台" - interval: 86400 - - cn-domain: - file: acl4ssr/ChinaDomain.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - cn-company-ip: - file: acl4ssr/ChinaCompanyIp.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - download: - file: acl4ssr/Download.yaml - behavior: classical - format: yaml - policy: "🎯 全球直连" - interval: 86400 - - proxy-gfw: - file: acl4ssr/ProxyGFWlist.yaml - behavior: classical - format: yaml - policy: "{{ main_policy }}" - interval: 86400 - - geoip-cn: - behavior: classical - format: text - policy: "🎯 全球直连" - payload: - - GEOIP,CN diff --git a/config/sources.yaml b/config/sources.yaml index c2ef4ec..b26a5f6 100644 --- a/config/sources.yaml +++ b/config/sources.yaml @@ -1,28 +1,483 @@ -airport-a: - enabled: true - display_name: A - kind: auto - url: ${AIRPORT_A_URL} - prefix: "[A] " - include_regex: "" - exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" +public_path: ${PUBLIC_PATH} -airport-b: - enabled: true - display_name: B - kind: auto - url: ${AIRPORT_B_URL} - headers: - User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" - prefix: "[B] " - include_regex: "" - exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" +sources: + airport-a: + enabled: true + display_name: A + kind: auto + url: ${AIRPORT_A_URL} + prefix: "[A] " + include_regex: "" + exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" -airport-c: - enabled: true - display_name: C - kind: auto - url: ${AIRPORT_C_URL} - prefix: "[C] " - include_regex: "" - exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" + airport-b: + enabled: true + display_name: B + kind: auto + url: ${AIRPORT_B_URL} + headers: + User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" + prefix: "[B] " + include_regex: "" + exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" + + airport-c: + enabled: true + display_name: C + kind: auto + url: ${AIRPORT_C_URL} + prefix: "[C] " + include_regex: "" + exclude_regex: "流量|重置|到期|续费|官网|离线|套餐" + +regions: + hk: + name: "🇭🇰 香港节点" + filter: "(?i)(港|hk|hong kong|hongkong)" + tolerance: 50 + tw: + name: "🇨🇳 台湾节点" + filter: "(?i)(台|新北|彰化|tw|taiwan)" + tolerance: 50 + sg: + name: "🇸🇬 狮城节点" + filter: "(?i)(新加坡|坡|狮城|sg|singapore)" + tolerance: 50 + jp: + name: "🇯🇵 日本节点" + filter: "(?i)(日本|东京|大阪|埼玉|jp|japan)" + tolerance: 50 + us: + name: "🇺🇲 美国节点" + filter: "(?i)(美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥|us|united states)" + tolerance: 150 + kr: + name: "🇰🇷 韩国节点" + filter: "(?i)(kr|korea|kor|首尔|韩|韓)" + tolerance: 50 + +selector_groups: + - name: "🎥 奈飞节点" + type: select + filter: "(?i)(nf|奈飞|解锁|netflix|media)" + +policy_groups: + - name: "📲 电报消息" + type: select + proxies: + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "🇸🇬 狮城节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇯🇵 日本节点" + - "🇺🇲 美国节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - "{{ direct_policy }}" + - name: "💬 Ai平台" + type: select + proxies: + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "🇺🇲 美国节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - "{{ direct_policy }}" + - name: "📹 油管视频" + type: select + proxies: + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "🇸🇬 狮城节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇯🇵 日本节点" + - "🇺🇲 美国节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - "{{ direct_policy }}" + - name: "🎥 奈飞视频" + type: select + proxies: + - "🎥 奈飞节点" + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "🇸🇬 狮城节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇯🇵 日本节点" + - "🇺🇲 美国节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - "{{ direct_policy }}" + - name: "🌍 国外媒体" + type: select + proxies: + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇺🇲 美国节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - "{{ direct_policy }}" + - name: "📢 谷歌" + type: select + proxies: + - "{{ main_policy }}" + - "{{ direct_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "Ⓜ️ 微软Bing" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "Ⓜ️ 微软云盘" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "Ⓜ️ 微软服务" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "🍎 苹果服务" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "🎮 游戏平台" + type: select + proxies: + - "{{ main_policy }}" + - "{{ direct_policy }}" + - "🇺🇲 美国节点" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "🎮 PT平台" + type: select + proxies: + - "{{ main_policy }}" + - "{{ direct_policy }}" + - "🇭🇰 香港节点" + - "🇨🇳 台湾节点" + - "🇸🇬 狮城节点" + - "🇯🇵 日本节点" + - "🇺🇲 美国节点" + - "🇰🇷 韩国节点" + - "{{ manual_policy }}" + - name: "🎯 全球直连" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - name: "🛑 广告拦截" + type: select + proxies: + - REJECT + - "{{ direct_policy }}" + - name: "🍃 应用净化" + type: select + proxies: + - REJECT + - "{{ direct_policy }}" + - name: "🐟 漏网之鱼" + type: select + proxies: + - "{{ direct_policy }}" + - "{{ main_policy }}" + - "{{ mixed_auto_policy }}" + - "{{ selector_groups }}" + - "{{ manual_policy }}" + +rules: + custom-proxy: + behavior: classical + format: text + policy: "{{ main_policy }}" + payload: + - DOMAIN-KEYWORD,cloudflare + - DOMAIN-KEYWORD,hetzner + - DOMAIN-KEYWORD,hdkylin + - DOMAIN-KEYWORD,steamusercontent + - DOMAIN-KEYWORD,steamcontent + - DOMAIN-KEYWORD,nintendo + - DOMAIN-KEYWORD,vscode + - DOMAIN-KEYWORD,btschool + + local-network: + file: acl4ssr/LocalAreaNetwork.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + unban: + file: acl4ssr/UnBan.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + reject: + file: acl4ssr/BanAD.yaml + behavior: classical + format: yaml + interval: 86400 + policy: "🛑 广告拦截" + + app-purify: + file: acl4ssr/BanProgramAD.yaml + behavior: classical + format: yaml + interval: 86400 + policy: "🍃 应用净化" + + google: + file: acl4ssr/Google.yaml + behavior: classical + format: yaml + policy: "📢 谷歌" + interval: 86400 + + google-cn: + file: acl4ssr/GoogleCN.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + steam-cn: + file: acl4ssr/SteamCN.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + microsoft-bing: + file: acl4ssr/Bing.yaml + behavior: classical + format: yaml + policy: "Ⓜ️ 微软Bing" + interval: 86400 + + microsoft-onedrive: + file: acl4ssr/OneDrive.yaml + behavior: classical + format: yaml + policy: "Ⓜ️ 微软云盘" + interval: 86400 + + microsoft: + file: acl4ssr/Microsoft.yaml + behavior: classical + format: yaml + policy: "Ⓜ️ 微软服务" + interval: 86400 + + apple: + file: acl4ssr/Apple.yaml + behavior: classical + format: yaml + policy: "🍎 苹果服务" + interval: 86400 + + telegram: + file: acl4ssr/Telegram.yaml + behavior: classical + format: yaml + policy: "📲 电报消息" + interval: 86400 + + ai: + file: acl4ssr/AI.yaml + behavior: classical + format: yaml + policy: "💬 Ai平台" + interval: 86400 + + openai: + file: acl4ssr/OpenAi.yaml + behavior: classical + format: yaml + policy: "💬 Ai平台" + interval: 86400 + + youtube: + file: acl4ssr/YouTube.yaml + behavior: classical + format: yaml + policy: "📹 油管视频" + interval: 86400 + + netflix: + file: acl4ssr/Netflix.yaml + behavior: classical + format: yaml + policy: "🎥 奈飞视频" + interval: 86400 + + proxy-media: + file: acl4ssr/ProxyMedia.yaml + behavior: classical + format: yaml + policy: "🌍 国外媒体" + interval: 86400 + + games-epic: + file: acl4ssr/Epic.yaml + behavior: classical + format: yaml + policy: "🎮 游戏平台" + interval: 86400 + + games-origin: + file: acl4ssr/Origin.yaml + behavior: classical + format: yaml + policy: "🎮 游戏平台" + interval: 86400 + + games-sony: + file: acl4ssr/Sony.yaml + behavior: classical + format: yaml + policy: "🎮 游戏平台" + interval: 86400 + + games-steam: + file: acl4ssr/Steam.yaml + behavior: classical + format: yaml + policy: "🎮 游戏平台" + interval: 86400 + + games-nintendo: + file: acl4ssr/Nintendo.yaml + behavior: classical + format: yaml + policy: "🎮 游戏平台" + interval: 86400 + + pt: + file: acl4ssr/PrivateTracker.yaml + behavior: classical + format: yaml + policy: "🎮 PT平台" + interval: 86400 + + cn-domain: + file: acl4ssr/ChinaDomain.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + cn-company-ip: + file: acl4ssr/ChinaCompanyIp.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + download: + file: acl4ssr/Download.yaml + behavior: classical + format: yaml + policy: "🎯 全球直连" + interval: 86400 + + proxy-gfw: + file: acl4ssr/ProxyGFWlist.yaml + behavior: classical + format: yaml + policy: "{{ main_policy }}" + interval: 86400 + + geoip-cn: + behavior: classical + format: text + policy: "🎯 全球直连" + payload: + - GEOIP,CN + +clients: + mihomo: + title: HomeLab Mihomo + provider_interval: 21600 + rule_interval: 86400 + test_url: https://www.gstatic.com/generate_204 + test_interval: 300 + main_policy: 🚀 节点选择 + source_policy: ☁️ 机场选择 + mixed_auto_policy: ♻️ 自动选择 + manual_policy: 🚀 手动切换 + direct_policy: DIRECT + mode: rule + allow_lan: true + ipv6: true + mixed_port: 7890 + socks_port: 7891 + log_level: info + + stash: + title: HomeLab Stash + provider_interval: 21600 + rule_interval: 86400 + test_url: https://www.gstatic.com/generate_204 + test_interval: 300 + main_policy: 🚀 节点选择 + source_policy: ☁️ 机场选择 + mixed_auto_policy: ♻️ 自动选择 + manual_policy: 🚀 手动切换 + direct_policy: DIRECT + mode: rule + ipv6: true + log_level: info diff --git a/docs/分阶段重构方案.md b/docs/分阶段重构方案.md deleted file mode 100644 index 15d635c..0000000 --- a/docs/分阶段重构方案.md +++ /dev/null @@ -1,686 +0,0 @@ -# sub-provider 分阶段重构方案 - -## 1. 文档目标 - -这份文档不是重新定义“最终想做成什么”,而是基于当前 `sub-provider` 已有实现,给出一版可逐步落地、每阶段都能交付可运行结果的重构路径。 - -当前项目已经具备以下基础能力: - -* 读取 `config/sources.yaml` -* 拉取和缓存上游订阅 -* 解析 Clash YAML / base64 URI / 明文 URI 订阅 -* 合并节点并生成 bundle YAML -* 生成 thin client 配置 -* 加载本地规则文件并拼接 `rules` -* 透传第一个源的 `Subscription-Userinfo` - -因此重构的目标不是“从零搭系统”,而是把现有静态配置驱动的生成器,逐步改造成: - -* 内部模块化更清晰 -* 配置存储可演进到数据库 -* 对外仍以完整 YAML 为主 -* 保留现有已验证能力 -* 后续可平滑加管理页和快照系统 - ---- - -## 2. 总体原则 - -### 原则 1:对外接口尽量稳定 - -在重构前几阶段,尽量不打断现有输出能力: - -* `/bundle/{client}.yaml` -* `/clients/{client}.yaml` -* `/providers/{name}.yaml` -* `/providers/merged.yaml` - -即使最终主路线偏向 bundle,也不建议在早期重构中直接删除 thin/provider 能力。 - -### 原则 2:先拆“配置来源”,再拆“表现层” - -当前项目的核心问题不是生成逻辑完全不可用,而是配置全部堆在 `sources.yaml` 里。 -优先级应是: - -1. 把配置从单一 YAML 拆到更清晰的数据结构 -2. 再引入数据库承载配置 -3. 最后再补管理页面 - -不要一上来把大量时间花在页面上。 - -### 原则 3:规则正文继续文件化 - -规则文件仍然保存在项目目录中,继续使用 Git 管理。 -数据库只存: - -* 规则模块元数据 -* 规则启用关系 -* 规则顺序 -* 规则绑定到哪个策略组 - -不把 ACL4SSR 规则逐条拆进数据库。 - -### 原则 4:保留动态选源能力 - -当前项目支持通过 `?sources=` 在请求级动态组合源,这个能力实际很有价值。 -重构后不建议退化成只能在 profile 中静态绑定源。 - -建议模型上同时支持: - -* profile 默认源集合 -* 请求级 `sources` override - ---- - -## 3. 对现有项目的基线判断 - -### 3.1 现状优点 - -当前代码结构虽然还偏轻量,但核心职责已经初步分开: - -* `subscriptions.py` 负责源抓取、解析、节点转换 -* `profiles.py` 负责 thin / bundle 配置组装 -* `rules.py` 负责规则文件加载和规则输出 -* `bundle_cache.py` / `fetch_cache.py` 负责磁盘缓存 - -这意味着重构时应该优先“抽象和替换内部数据来源”,而不是推倒服务层。 - -### 3.2 现状主要问题 - -当前主要限制有: - -* `sources.yaml` 同时承载源配置、地区分组、业务策略组、规则绑定、客户端配置 -* 配置模型偏静态,不利于面板管理 -* bundle 缓存仍是 TTL 模式,不是内容指纹模式 -* 没有配置快照、生成快照、审计能力 -* 当前没有数据库层,也没有迁移体系 - -### 3.3 结论 - -最稳的路线不是“大重写”,而是: - -1. 保持现有生成骨架 -2. 把配置与渲染逻辑进一步解耦 -3. 再把配置落进数据库 -4. 最后加面板和快照 - ---- - -## 4. 目标架构 - -推荐演进成如下结构: - -```text -请求 - -> API 路由 - -> Profile 解析 - -> Source 选择 - -> Source 拉取 / 缓存 - -> 节点解析 / 标准化 / 处理 - -> 策略组装配 - -> 规则模块解析 - -> 完整 YAML 渲染 - -> 结果缓存 / 快照 - -> 返回响应头与 YAML -``` - -配置来源则逐步从: - -```text -sources.yaml -``` - -演进为: - -```text -数据库配置 + 本地规则资产文件 + 少量默认 YAML 模板 -``` - ---- - -## 5. 分阶段路线 - -建议拆成五个阶段,每个阶段结束都能保留一个稳定可运行版本。 - -### 阶段 0:重构准备与基线冻结 - -目标: - -* 不改行为,先明确现有能力边界 -* 为后续重构建立可回归的基线 - -本阶段任务: - -1. 盘点当前公开接口、查询参数、响应头语义 -2. 记录当前 `sources.yaml` 中各配置块的职责 -3. 为核心生成路径补最小测试 -4. 输出一份“当前行为基线”文档 - -验收标准: - -* 至少覆盖以下场景: - * 单源 provider - * 多源 merged provider - * bundle 输出 - * thin 输出 - * 第一个源配额头透传 -* 有一组固定输入可验证当前输出结构不被意外破坏 - -建议产物: - -* `tests/` 下最小回归测试 -* `docs/当前行为基线.md` - -说明: - -这一阶段很重要。没有基线,后面数据库化后很难判断是“重构带来的结构变化”,还是“偷偷改了行为”。 - ---- - -### 阶段 1:配置解耦,但仍以 YAML 为配置源 - -目标: - -* 先把“一个巨大的 `sources.yaml`”拆成多个清晰模块 -* 暂时不引入数据库 -* 不改变线上运行方式 - -本阶段任务: - -1. 拆分当前 `sources.yaml`: - * `config/sources.yaml` - * `config/clients.yaml` - * `config/rule-bindings.yaml` - * `config/policy-groups.yaml` - * `config/regions.yaml` -2. 新增统一配置加载层,把多个 YAML 聚合成当前运行所需的内部配置对象 -3. 把当前 `AppConfig` 进一步拆开,减少“大一统模型” -4. 让 `main.py` 不再直接依赖单个全局 `app_config` -5. 把“配置加载”和“配置解释”职责从“服务逻辑”中剥离 - -建议目录演进: - -```text -app/ - config.py - main.py - models/ - source.py - client.py - profile.py - rule.py - group.py - services/ - config_loader.py - subscriptions.py - profiles.py - rules.py -``` - -验收标准: - -* 对外接口路径和行为保持不变 -* `bundle` 与 `thin` 输出内容结构基本一致 -* 已不再依赖单个巨型 YAML 文件 - -这一阶段收益: - -* 先把系统结构理顺 -* 后续接数据库时,只需要替换配置仓储层,不需要大改生成器本身 - ---- - -### 阶段 2:引入数据库配置层,保留 YAML 兼容导入 - -目标: - -* 让配置从静态文件迁移到数据库 -* 但仍支持从 YAML 导入初始数据 - -本阶段任务: - -1. 引入 SQLAlchemy 与 Alembic -2. 增加 `DATABASE_URL` -3. 建立第一批核心表 -4. 实现配置仓储层 -5. 提供 YAML 初始化导入脚本 - -推荐第一批表: - -* `sources` -* `profiles` -* `profile_sources` -* `rule_modules` -* `profile_rule_modules` -* `policy_groups` -* `profile_overrides` - -推荐暂不引入的表: - -* `generated_artifacts` -* `sync_logs` -* `rule_module_versions` - -原因: - -先把“当前运行配置”迁过去,比一开始就把快照、审计、版本全部做全更稳。 - -#### 推荐字段设计 - -`sources` - -* `id` -* `key` -* `name` -* `enabled` -* `kind` -* `url` -* `display_name` -* `headers_json` -* `include_regex` -* `exclude_regex` -* `prefix` -* `suffix` -* `cache_ttl_seconds` -* `created_at` -* `updated_at` - -`profiles` - -* `id` -* `key` -* `name` -* `client_type` -* `description` -* `enabled` -* `allow_lan` -* `ipv6` -* `mixed_port` -* `socks_port` -* `mode` -* `log_level` -* `main_policy` -* `source_policy` -* `mixed_auto_policy` -* `manual_policy` -* `direct_policy` -* `test_url` -* `test_interval` -* `provider_interval` -* `rule_interval` -* `created_at` -* `updated_at` - -`profile_sources` - -* `id` -* `profile_id` -* `source_id` -* `order_index` -* `enabled` - -`rule_modules` - -* `id` -* `key` -* `name` -* `description` -* `file_path` -* `behavior` -* `format` -* `default_policy` -* `default_no_resolve` -* `default_payload_json` -* `category` -* `created_at` -* `updated_at` - -`profile_rule_modules` - -* `id` -* `profile_id` -* `rule_module_id` -* `enabled` -* `order_index` -* `policy_override` -* `payload_override_json` -* `no_resolve_override` - -`policy_groups` - -* `id` -* `profile_id` -* `name` -* `group_kind` -* `type` -* `order_index` -* `filter_regex` -* `proxies_json` -* `url` -* `interval` -* `tolerance` -* `enabled` - -这里的 `group_kind` 建议至少支持: - -* `static` -* `filter` -* `generated` - -这比单纯用一个 `config_json` 更适合当前项目,因为现有组装里同时存在静态组和按 regex 选节点的组。 - -#### 仓储层建议 - -新增仓储抽象,例如: - -* `SourceRepository` -* `ProfileRepository` -* `RuleModuleRepository` -* `PolicyGroupRepository` - -让生成服务只依赖仓储接口,而不是依赖数据库实现细节。 - -验收标准: - -* 使用数据库也能生成与阶段 1 基本一致的 bundle/thin 配置 -* 可以从原 YAML 导入初始配置 -* 切换配置来源时,渲染层无需大改 - ---- - -### 阶段 3:渲染流水线模块化与输出主路线收敛 - -目标: - -* 把“源处理、规则处理、组装渲染”彻底做成稳定流水线 -* 明确 bundle 是主输出路线 -* thin/provider 进入兼容保留状态 - -本阶段任务: - -1. 拆分服务层职责: - * `source_fetcher.py` - * `source_parser.py` - * `proxy_processor.py` - * `policy_group_builder.py` - * `rule_resolver.py` - * `profile_renderer.py` -2. 引入统一内部节点模型 -3. 引入统一内部“已解析 profile”模型 -4. 将当前 `build_bundle_profile()` 的逻辑拆成多步处理 -5. 清理当前 token 展开逻辑,明确保留哪些模板 token - -#### 内部模型建议 - -不要把节点模型收得太死。建议: - -```python -class ProxyNode(BaseModel): - name: str - type: str - server: str | None = None - port: int | None = None - udp: bool = True - tags: list[str] = Field(default_factory=list) - attrs: dict[str, Any] = Field(default_factory=dict) -``` - -其中: - -* 通用字段单独保留 -* 协议特有字段放 `attrs` -* 最终输出时再按协议合并回 YAML 结构 - -这样比单纯的 `raw` 更可控。 - -#### 规则系统建议 - -这一阶段不要急着把所有规则文件改成 `{{ target_group }}` 模板格式。 -先保留当前模式: - -* 文件内容仍是 payload -* policy 在 profile 绑定关系上决定 - -原因: - -* 当前 thin 模式的 `rule-providers` 仍依赖 payload 文件输出 -* 先保留兼容性,后面再决定是否升级规则文件模板化 - -验收标准: - -* bundle 渲染主路径清晰可测 -* 服务层之间依赖方向清楚 -* 配置仓储和渲染器分离 - ---- - -### 阶段 4:管理接口与最小面板 - -目标: - -* 提供最小配置管理能力 -* 不做复杂前后端分离 - -本阶段任务: - -1. 增加管理 API: - * source CRUD - * profile CRUD - * rule module 查看与 profile 绑定管理 - * policy group 查看与编辑 -2. 增加预览接口 -3. 增加最小页面: - * sources - * profiles - * rule bindings - * preview -4. 提供 profile 级 bundle 下载入口 - -建议接口: - -* `GET /api/sources` -* `POST /api/sources` -* `PUT /api/sources/{id}` -* `GET /api/profiles` -* `POST /api/profiles` -* `PUT /api/profiles/{id}` -* `GET /api/profiles/{id}/rules` -* `PUT /api/profiles/{id}/rules` -* `GET /api/profiles/{id}/groups` -* `PUT /api/profiles/{id}/groups` -* `POST /api/profiles/{id}/preview` -* `GET /profiles/{key}/bundle.yaml` - -面板技术建议: - -* Jinja2 -* HTMX 或最少量原生 JS - -不建议此时做: - -* React/Vue 前后端分离 -* 复杂权限系统 -* 在线全文规则编辑器 - -验收标准: - -* 不改代码即可新增一个 source -* 不改 YAML 文件即可调整 profile 规则顺序 -* 页面可直接预览 bundle 输出 - ---- - -### 阶段 5:快照、缓存升级与可追踪性 - -目标: - -* 从“能生成”升级到“可追踪、可复现、可排查” - -本阶段任务: - -1. 增加 `generated_artifacts` -2. 增加内容指纹缓存 -3. 增加源同步状态记录 -4. 增加简单 diff 能力 -5. 增加错误展示 - -推荐新增表: - -`generated_artifacts` - -* `id` -* `profile_id` -* `request_sources_json` -* `content` -* `content_hash` -* `source_hash` -* `rules_hash` -* `profile_hash` -* `headers_json` -* `generated_at` - -`source_sync_logs` - -* `id` -* `source_id` -* `status` -* `error_message` -* `response_headers_json` -* `content_hash` -* `created_at` - -#### 缓存策略建议 - -当前项目 bundle 缓存是 TTL 模式。后续应逐步改为: - -* fetch cache: TTL -* parsed snapshot cache: 源内容 hash -* bundle cache: `source_hash + rules_hash + profile_hash + request_sources` - -这样才能真正做到: - -* 配置没变就稳定复用 -* 配置变了就精准失效 - -验收标准: - -* 能查看某个 profile 最近几次生成记录 -* 能知道本次 bundle 为什么重新生成 -* 能追踪某个源最近一次同步是否失败 - ---- - -## 6. 推荐实施顺序 - -如果按投入产出比排序,建议实际开发顺序如下: - -1. 阶段 0 -2. 阶段 1 -3. 阶段 2 -4. 阶段 3 -5. 阶段 4 -6. 阶段 5 - -其中真正的“第一版可交付里程碑”建议定在阶段 2 结束时。 - -因为到了阶段 2,已经具备: - -* 数据库存储配置 -* 可继续生成完整 YAML -* 配置不再绑死在单文件 YAML 中 - -这时即使还没有面板,系统内核已经完成了最关键升级。 - ---- - -## 7. 每阶段风险点 - -### 阶段 1 风险 - -风险: - -* 拆 YAML 时容易出现字段兼容问题 - -控制方式: - -* 先保留兼容加载层 -* 老配置格式在一段时间内继续可读 - -### 阶段 2 风险 - -风险: - -* 数据建模过度,导致表过多、实现过重 - -控制方式: - -* 第一批只建运行必需表 -* 快照与日志延后 - -### 阶段 3 风险 - -风险: - -* 过早重写规则系统,导致 thin 模式兼容性下降 - -控制方式: - -* 先保留 payload 模式 -* 规则模板化放后续增量处理 - -### 阶段 4 风险 - -风险: - -* 过早投入页面开发,拖慢主流程 - -控制方式: - -* 先 API 后页面 -* 页面只做最小可用 - -### 阶段 5 风险 - -风险: - -* 快照和缓存逻辑做得太复杂,维护成本上升 - -控制方式: - -* 先做内容哈希与生成记录 -* diff 与审计能力逐步补 - ---- - -## 8. 明确不建议的做法 - -1. 不建议一开始就删除当前 thin/provider 能力 -2. 不建议一开始就把所有规则文件模板化 -3. 不建议把 profile 的源集合只做成一个 `source_ids_json` -4. 不建议把策略组全塞进一个不透明 `config_json` -5. 不建议先做复杂前端再补业务内核 -6. 不建议先做快照系统再做数据库配置层 - ---- - -## 9. 建议的第一批实际改动 - -如果下一步开始真正动代码,建议先做这几件事: - -1. 新建阶段 0 的测试和基线文档 -2. 拆分 `sources.yaml`,完成阶段 1 -3. 引入 SQLAlchemy/Alembic 和最小表结构 -4. 增加 YAML -> DB 导入脚本 -5. 让 bundle 渲染从数据库读取 profile / source / rules 配置 - -这五步完成后,再去做页面,整体节奏会稳很多。 - ---- - -## 10. 一句话结论 - -`sub-provider` 的重构最稳路线是: - -> 先做配置解耦,再做数据库配置层,再做渲染流水线固化,最后补管理页面和快照系统。 - -这样每一阶段都能保留一个可运行版本,也最符合当前项目已经有代码基础的现实情况。 diff --git a/docs/改造方案.md b/docs/改造方案.md deleted file mode 100644 index f6421fc..0000000 --- a/docs/改造方案.md +++ /dev/null @@ -1,976 +0,0 @@ -下面这份你可以直接丢给 Codex。 -我把我们刚刚聊出来的**最终拍板结论**和**完整实现方案**都整理进去了。 - ---- - -# sub-provider 重构方案(最终版,完整 YAML 路线) - -## 1. 最终结论 - -本项目后续重构,采用以下最终选择: - -### 对外输出 - -* **统一输出单个完整 YAML** -* 不强依赖客户端支持 `rule-providers` / `proxy-providers` -* 目标是优先保证: - - * 兼容性 - * 可调试性 - * 易分发 - * 易维护 - -### 对内实现 - -* 内部仍然采用**模块化**设计 -* 在 sub-provider 内部完成: - - * 源拉取与缓存 - * 节点解析与标准化 - * 节点筛选、重命名、去重 - * 规则模块组合 - * 策略组装配 - * DNS / TUN / 通用配置拼装 - * 最终完整 YAML 渲染 - -### 规则存储方式 - -* **规则正文继续放项目文件** -* 采用类似 ACL4SSR 的维护方式: - - * 一个类别的规则放一个文件 - * 规则文件便于 Git 管理、diff、回滚、手工维护 -* 不把 ACL4SSR 大量规则逐条入库 - -### 规则组合与面板配置 - -* **规则的组合关系、启用状态、绑定的策略组、顺序、参数等放 SQLite** -* 面板配置的是: - - * 选哪些规则模块 - * 每个模块绑定哪个策略组 - * 模块顺序 - * 模块参数 - * profile 配置 - * 源配置 - * 少量 prepend / append 自定义规则 - -### 数据库选择 - -* **默认数据库:SQLite** -* 原因: - - * 当前项目是单服务、轻量配置中心、生成器型应用 - * SQLite 部署和迁移成本最低 - * 最适合当前阶段快速落地 -* 代码层要保留未来切 PostgreSQL 的能力: - - * 使用 SQLAlchemy - * 使用 Alembic - * 用 `DATABASE_URL` 配置数据库连接 -* 当前不推荐 Mongo 作为主库 -* 当前不优先推荐 MySQL - ---- - -## 2. 目标 - -将现有 Python sub-provider 项目重构为一个: - -* 可面板管理 -* 配置解耦 -* 规则模块化 -* 输出完整 YAML -* 可缓存 -* 可预览 -* 可追踪生成结果 - -的配置生成系统。 - ---- - -## 3. 项目边界 - -### 本阶段要做 - -1. 面板化管理订阅源 -2. 规则模块化 -3. 规则组合关系数据库化 -4. 支持多个输出 Profile -5. 服务端拼装生成完整 YAML -6. 做缓存和快照 -7. 支持预览和下载 - -### 本阶段不做 - -1. 不强制对外输出 provider 模式 -2. 不做“在线全文编辑 ACL4SSR 大规则文件”为主交互方式 -3. 不做复杂多用户权限系统 -4. 不做重型前后端分离 -5. 不把所有规则全文放 SQLite -6. 不引入 Mongo 做主存储 - ---- - -## 4. 架构原则 - -### 原则 1:外部简单,内部灵活 - -客户端只拿一个完整 YAML。 -服务端内部怎么拆、怎么缓存、怎么组合,都由 sub-provider 负责。 - -### 原则 2:规则内容文件化,组合关系数据库化 - -规则正文属于“资产”,继续放文件。 -面板管理的是“配置和组合关系”,放 SQLite。 - -### 原则 3:90% 的配置修改通过“选模块 + 改参数”完成 - -不要把面板做成“大文本框配置编辑器”。 - -### 原则 4:优先可维护,而不是一开始就追求炫技 - -先把结构跑顺,再考虑高级模式。 - ---- - -## 5. 总体架构 - -```text -输入源 - -> 拉取缓存 - -> 原始内容缓存 - -> 解析节点 - -> 节点标准化 - -> 节点处理(过滤/去重/重命名/分类) - -> 策略组生成 - -> 规则模块加载 - -> 规则模块参数渲染 - -> 规则按顺序拼接 - -> 插入 prepend/append 自定义规则 - -> 渲染完整 YAML - -> 结果缓存 / 生成快照 - -> 提供下载 / 预览 / HEAD 信息 -``` - ---- - -## 6. 推荐技术栈 - -### 后端 - -* FastAPI - -### 数据库 - -* SQLite(默认) -* SQLAlchemy ORM -* Alembic migration - -### 模板 - -* Jinja2 - -### 前端 - -* 优先服务端渲染 -* 可用 Jinja2 + HTMX 或简单模板页 -* 先不要求 React/Vue 前后端分离 - -### 缓存 - -* 轻量场景下可先用: - - * SQLite 表 - * 本地文件缓存 -* 不必一上来引入 Redis - ---- - -## 7. 目录结构建议 - -```text -app/ - api/ - routes_sources.py - routes_profiles.py - routes_rules.py - routes_preview.py - core/ - config.py - database.py - cache.py - db/ - base.py - session.py - models/ - source.py - profile.py - rule_module.py - policy_group.py - artifact.py - schemas/ - source.py - profile.py - rule_module.py - services/ - source_fetcher.py - source_parser.py - proxy_normalizer.py - proxy_processor.py - policy_group_builder.py - rule_loader.py - rule_renderer.py - profile_renderer.py - artifact_service.py - templates/ - base.html - sources.html - profiles.html - rules.html - preview.html - main.py - -rules/ - modules/ - lan.list - private.list - cn_domain.list - cn_ip.list - apple.list - microsoft.list - github.list - telegram.list - openai.list - streaming.list - ads.list - final_proxy.list - final_direct.list - - presets/ - minimal.yaml - daily.yaml - router.yaml - - group_templates/ - basic.yaml - ai.yaml - streaming.yaml - -data/ - app.db - cache/ - raw_sources/ - parsed_sources/ - generated/ - artifacts/ - -migrations/ -tests/ -``` - ---- - -## 8. 规则系统设计 - -## 8.1 规则正文:继续放文件 - -规则正文文件继续按 ACL4SSR 风格维护。 - -例如: - -* `rules/modules/apple.list` -* `rules/modules/openai.list` -* `rules/modules/telegram.list` -* `rules/modules/cn_domain.list` -* `rules/modules/final_proxy.list` - -### 推荐格式 - -规则文件使用模板变量,不直接写死策略组: - -```text -DOMAIN-SUFFIX,openai.com,{{ target_group }} -DOMAIN-SUFFIX,chatgpt.com,{{ target_group }} -DOMAIN-SUFFIX,oaistatic.com,{{ target_group }} -DOMAIN-SUFFIX,auth0.openai.com,{{ target_group }} -``` - -这样同一个规则模块可复用到不同策略组。 - ---- - -## 8.2 规则模块:放 SQLite - -SQLite 里存规则模块元数据,而不是规则全文。 - -### 表:`rule_modules` - -字段建议: - -* `id` -* `key` -* `name` -* `description` -* `file_path` -* `category` -* `default_enabled` -* `default_order` -* `default_target_policy` -* `params_schema_json` -* `created_at` -* `updated_at` - -### 示例 - -```json -{ - "key": "openai", - "name": "OpenAI", - "file_path": "rules/modules/openai.list", - "category": "service", - "default_enabled": true, - "default_order": 80, - "default_target_policy": "🤖 AI", - "params_schema_json": { - "target_group": { - "type": "string", - "default": "🤖 AI" - } - } -} -``` - ---- - -## 8.3 Profile 中的规则组合:放 SQLite - -### 表:`profile_rule_modules` - -字段建议: - -* `id` -* `profile_id` -* `module_id` -* `enabled` -* `order_index` -* `target_policy` -* `params_json` - -含义: - -* 这个 profile 是否启用这个模块 -* 顺序是什么 -* 最终绑定到哪个策略组 -* 参数是什么 - ---- - -## 8.4 规则预设 - -可选支持“预设”。 - -例如: - -* `minimal` -* `daily` -* `router` - -但预设只作为**初始化模板**,最终仍落到具体 profile 配置里。 - -不要求上线初期就把预设做得很复杂。 - ---- - -## 9. 策略组系统设计 - -策略组不要硬编码死在一个大 YAML 模板里。 -也要做成可配置对象。 - -### 表:`policy_groups` - -字段建议: - -* `id` -* `profile_id` -* `name` -* `type` -* `order_index` -* `config_json` - -### `config_json` 示例 - -```json -{ - "proxies": ["DIRECT", "REJECT", "香港节点", "日本节点"], - "include_all_nodes": false, - "filter": "HK|Hong Kong" -} -``` - -### 支持的策略组类型 - -* `select` -* `url-test` -* `fallback` -* `load-balance` - -### 建议的默认组 - -* `🚀 节点选择` -* `🤖 AI` -* `📺 流媒体` -* `🍎 苹果服务` -* `📲 Telegram` -* `🌍 国外网站` -* `🇨🇳 国内网站` - ---- - -## 10. 源管理设计 - -### 表:`sources` - -字段建议: - -* `id` -* `name` -* `type` - 值可为: - - * `base64` - * `url` - * `link` - * `static` -* `content` -* `headers_json` -* `enabled` -* `priority` -* `update_interval_sec` -* `rename_rules_json` -* `filter_rules_json` -* `dedupe_policy` -* `last_sync_at` -* `last_sync_status` -* `last_error` -* `created_at` -* `updated_at` - -### 说明 - -这里的源是原始输入源,不是给客户端的 provider。 - ---- - -## 11. Profile 设计 - -Profile 是最终对外输出的配置单元。 - -例如: - -* iPhone 配置 -* Windows 配置 -* OpenWrt 配置 -* Apple TV 配置 -* 精简版配置 -* 完整版配置 - -### 表:`profiles` - -字段建议: - -* `id` -* `name` -* `description` -* `source_ids_json` -* `dns_template` -* `tun_enabled` -* `udp_enabled` -* `append_subscription_info` -* `expose_head_info` -* `enabled` -* `created_at` -* `updated_at` - ---- - -## 12. 自定义覆盖设计 - -为了兼容高级用户需求,支持少量覆盖,但不做全文编辑器。 - -### 表:`profile_overrides` - -字段建议: - -* `id` -* `profile_id` -* `custom_rules_prepend` -* `custom_rules_append` -* `custom_proxy_groups` -* `custom_dns_patch` -* `custom_yaml_patch` - -### 原则 - -只提供: - -* prepend rules -* append rules -* 少量 patch - -不鼓励直接在面板里手写全部 YAML。 - ---- - -## 13. 缓存设计 - -## 13.1 原始源缓存 - -缓存拉回来的原始内容。 - -目的: - -* 避免频繁请求订阅源 -* 降低上游压力 -* 出问题可复现 - ---- - -## 13.2 解析结果缓存 - -把原始内容解析为统一节点模型后缓存。 - -目的: - -* 多 profile 复用解析结果 -* 避免重复解析 - ---- - -## 13.3 最终 YAML 缓存 - -同一个 profile 在以下内容不变时,直接返回缓存: - -* 订阅源内容未变 -* profile 配置未变 -* 规则模块未变 -* 策略组未变 - -缓存键建议基于 hash: - -* 源快照 hash -* 规则快照 hash -* profile 配置 hash - ---- - -## 14. 生成快照设计 - -### 表:`generated_artifacts` - -字段建议: - -* `id` -* `profile_id` -* `version` -* `content` -* `content_hash` -* `source_snapshot_json` -* `rule_snapshot_json` -* `generated_at` - -### 用途 - -* 预览 -* diff -* 回滚 -* 调试 -* 追踪“为什么这次生成结果变了” - ---- - -## 15. 内部统一节点模型 - -建议在解析层统一成一个内部对象。 - -### Python 草案 - -```python -from pydantic import BaseModel -from typing import Any - -class ProxyNode(BaseModel): - name: str - type: str - server: str - port: int - udp: bool = True - tags: list[str] = [] - raw: dict[str, Any] = {} -``` - -对不同类型协议(ss、vmess、vless、trojan、hysteria 等)额外字段可放在 `raw` 里。 - ---- - -## 16. 生成流水线 - -## 第 1 步:拉取源 - -* 读取启用的 sources -* 获取原始订阅内容 -* 更新缓存 -* 记录同步状态 - -## 第 2 步:解析源 - -* 解析 base64 -* 解析单链接 -* 解析远程订阅 -* 标准化成统一节点模型 - -## 第 3 步:节点处理 - -* 去重 -* 重命名 -* 按国家/协议/标签分类 -* 过滤无效节点 -* 应用 profile 的筛选逻辑 - -## 第 4 步:生成策略组 - -* 读取 profile 绑定的 policy groups -* 根据节点分类与过滤规则生成 `proxy-groups` - -## 第 5 步:加载规则模块 - -* 读取 profile 启用的规则模块 -* 按顺序加载规则文件 -* 渲染模板变量(如 `target_group`) - -## 第 6 步:组装规则 - -* 拼接所有规则模块 -* 插入 prepend rules -* 插入 append rules -* 保证 `MATCH` 规则最后输出 - -## 第 7 步:生成完整 YAML - -输出包含: - -* mixed-port / socks-port / redir-port / tproxy-port(按需) -* mode -* log-level -* dns -* tun -* proxies -* proxy-groups -* rules - -## 第 8 步:缓存与快照 - -* 计算 hash -* 命中缓存则复用 -* 否则生成新 artifact - ---- - -## 17. 输出设计 - -### 对外输出接口 - -* `/profiles/{id}/clash.yaml` -* `/profiles/{id}/download` -* `/profiles/{id}/preview` -* `/profiles/{id}/head` - -### 输出原则 - -* 默认返回完整 YAML -* 对客户端透明 -* 客户端无需理解内部模块化结构 - -### 可选响应头 - -支持将源站订阅信息透传到最终响应头,例如: - -* `subscription-userinfo` -* 其他流量额度相关头 - -如果多个源混合: - -* 默认只透传第一个主源的额度信息 -* 或后续设计聚合策略,但不是当前必做 - ---- - -## 18. API 草案 - -## 源管理 - -* `GET /api/sources` -* `POST /api/sources` -* `PUT /api/sources/{id}` -* `DELETE /api/sources/{id}` -* `POST /api/sources/{id}/test` -* `POST /api/sources/{id}/sync` - -## 规则模块 - -* `GET /api/rule-modules` -* `GET /api/rule-modules/{id}` -* `PUT /api/rule-modules/{id}` - -## Profile - -* `GET /api/profiles` -* `POST /api/profiles` -* `PUT /api/profiles/{id}` -* `DELETE /api/profiles/{id}` - -## Profile 规则配置 - -* `GET /api/profiles/{id}/rules` -* `PUT /api/profiles/{id}/rules` - -## Profile 策略组配置 - -* `GET /api/profiles/{id}/policy-groups` -* `PUT /api/profiles/{id}/policy-groups` - -## 预览与生成 - -* `POST /api/profiles/{id}/preview` -* `POST /api/profiles/{id}/generate` -* `GET /api/profiles/{id}/artifacts` -* `GET /api/artifacts/{id}` - ---- - -## 19. 面板设计原则 - -### 普通模式 - -允许: - -* 配源 -* 选 profile -* 勾选规则模块 -* 选择规则模块绑定的策略组 -* 调模块顺序 -* 加 prepend / append 规则 -* 预览生成结果 - -### 高级模式 - -允许: - -* 编辑少量覆盖配置 -* 自定义组模板 -* 导入自定义规则模块 - -### 不建议 - -* 直接在线编辑整份 ACL4SSR 规则大文件作为主要操作方式 - ---- - -## 20. 示例规则模块 - -## `rules/modules/openai.list` - -```text -DOMAIN-SUFFIX,openai.com,{{ target_group }} -DOMAIN-SUFFIX,chatgpt.com,{{ target_group }} -DOMAIN-SUFFIX,oaistatic.com,{{ target_group }} -DOMAIN-SUFFIX,auth0.openai.com,{{ target_group }} -``` - -## `rules/modules/apple.list` - -```text -DOMAIN-SUFFIX,apple.com,{{ target_group }} -DOMAIN-SUFFIX,icloud.com,{{ target_group }} -DOMAIN-SUFFIX,itunes.apple.com,{{ target_group }} -DOMAIN-SUFFIX,apps.apple.com,{{ target_group }} -``` - ---- - -## 21. 示例最终输出 YAML 骨架 - -```yaml -mixed-port: 7890 -allow-lan: true -mode: rule -log-level: info - -dns: - enable: true - ipv6: false - nameserver: - - 223.5.5.5 - - 119.29.29.29 - -proxies: - - name: 香港节点 01 - type: ss - server: 1.2.3.4 - port: 443 - cipher: aes-128-gcm - password: xxxx - -proxy-groups: - - name: 🚀 节点选择 - type: select - proxies: - - 香港节点 01 - - 日本节点 01 - - DIRECT - - - name: 🤖 AI - type: select - proxies: - - 🚀 节点选择 - - 香港节点 01 - - 日本节点 01 - -rules: - - DOMAIN-SUFFIX,openai.com,🤖 AI - - DOMAIN-SUFFIX,chatgpt.com,🤖 AI - - DOMAIN-SUFFIX,apple.com,🍎 苹果服务 - - GEOIP,CN,DIRECT - - MATCH,🚀 节点选择 -``` - ---- - -## 22. 数据库配置要求 - -### 默认 - -```env -DATABASE_URL=sqlite:///./data/app.db -``` - -### 未来切 PostgreSQL - -```env -DATABASE_URL=postgresql+psycopg://user:pass@postgres:5432/subprovider -``` - -### 要求 - -* 所有数据库访问都走 SQLAlchemy -* migration 走 Alembic -* 不写死 SQLite 特性到业务逻辑中 - ---- - -## 23. 开发阶段建议 - -## 第一阶段:解耦与最小可用 - -目标: - -* 先跑通结构 - -任务: - -1. 引入 SQLAlchemy + Alembic -2. 建 sources / profiles / rule_modules / profile_rule_modules / policy_groups / artifacts 表 -3. 将现有规则整理为 `rules/modules/*.list` -4. 将现有单体生成逻辑拆成 service -5. 支持生成一个完整 YAML -6. 提供最简预览页面 - ---- - -## 第二阶段:面板化 - -目标: - -* 基础可用 - -任务: - -1. 源管理页面 -2. Profile 管理页面 -3. 规则模块勾选和排序页面 -4. 策略组配置页面 -5. 预览和下载页面 - ---- - -## 第三阶段:增强能力 - -目标: - -* 更稳定、更可追踪 - -任务: - -1. 快照与 diff -2. 生成缓存优化 -3. 同步日志和错误展示 -4. 自定义规则导入 -5. 自定义组模板 - ---- - -## 24. 明确不推荐的方向 - -1. 不推荐把 ACL4SSR 规则逐条入 SQLite -2. 不推荐面板主交互是“编辑一万条规则文本” -3. 不推荐一开始就做 rule-provider / proxy-provider 对外发布 -4. 不推荐 Mongo 作为主配置库 -5. 不推荐先做复杂权限系统 -6. 不推荐先做重前端再补业务逻辑 - ---- - -## 25. 给 Codex 的明确执行要求 - -请按以下要求重构项目: - -1. 保留对外输出为单个完整 YAML -2. 将内部生成逻辑改为模块化流水线 -3. 规则正文继续使用文件存储 -4. 使用 SQLite 存储: - - * 源配置 - * profile 配置 - * 规则模块元数据 - * profile 与规则模块的绑定关系 - * 策略组配置 - * 生成快照 -5. 使用 SQLAlchemy + Alembic,数据库 URL 可配置 -6. 代码结构按 service 分层,避免单文件堆逻辑 -7. 首先实现最小可运行版本: - - * 能配置 source - * 能配置 profile - * 能选择规则模块 - * 能选择目标策略组 - * 能生成并预览完整 YAML -8. 不要实现对外 provider 模式作为默认路线 -9. 不要将规则正文全文入库 -10. 不要将面板设计成规则全文编辑器 - ---- - -## 26. 最终一句话总结 - -本项目的最终路线是: - -> **内部模块化、缓存化、可配置化** -> **对外输出完整单文件 YAML** -> **规则正文继续文件维护** -> **规则组合关系和面板配置放 SQLite** - -这条路线最符合当前项目阶段,也最稳。 - ---- - -如果你要,我下一条可以继续给你一份**更像任务单的 Codex 执行拆分版**,我会按“先改哪些文件、再改哪些文件、每步验收什么”来写。