step1
This commit is contained in:
69
app/conf_models.py
Normal file
69
app/conf_models.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class ConfSource(BaseModel):
|
||||||
|
key: str
|
||||||
|
source_type: Literal["url", "file", "inline", "base64"] = "url"
|
||||||
|
value: str
|
||||||
|
enabled: bool = True
|
||||||
|
cache_ttl: int | None = None
|
||||||
|
options: dict[str, str] = Field(default_factory=dict)
|
||||||
|
line_no: int
|
||||||
|
|
||||||
|
|
||||||
|
class ConfSelector(BaseModel):
|
||||||
|
key: str
|
||||||
|
regex: str
|
||||||
|
line_no: int
|
||||||
|
|
||||||
|
|
||||||
|
class ConfGroup(BaseModel):
|
||||||
|
name: str
|
||||||
|
group_type: str
|
||||||
|
tokens: list[str] = Field(default_factory=list)
|
||||||
|
raw: str
|
||||||
|
line_no: int
|
||||||
|
|
||||||
|
|
||||||
|
class ConfModule(BaseModel):
|
||||||
|
key: str
|
||||||
|
path: str
|
||||||
|
policy: str
|
||||||
|
order: int
|
||||||
|
enabled: bool = True
|
||||||
|
line_no: int
|
||||||
|
|
||||||
|
def resolved_path(self, config_dir: Path) -> Path:
|
||||||
|
return (config_dir / self.path).resolve()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfBuiltin(BaseModel):
|
||||||
|
key: str
|
||||||
|
builtin_type: Literal["GEOIP", "FINAL"]
|
||||||
|
value: str = ""
|
||||||
|
policy: str
|
||||||
|
order: int
|
||||||
|
enabled: bool = True
|
||||||
|
line_no: int
|
||||||
|
|
||||||
|
|
||||||
|
class ConfBaseConfig(BaseModel):
|
||||||
|
listen: str | None = None
|
||||||
|
output_dir: str | None = None
|
||||||
|
cache_dir: str | None = None
|
||||||
|
mode: str | None = None
|
||||||
|
allow_lan: bool | None = None
|
||||||
|
log_level: str | None = None
|
||||||
|
ipv6: bool | None = None
|
||||||
|
append_userinfo_header: bool | None = None
|
||||||
|
userinfo_source_policy: str | None = None
|
||||||
|
sources: list[ConfSource] = Field(default_factory=list)
|
||||||
|
selectors: list[ConfSelector] = Field(default_factory=list)
|
||||||
|
groups: list[ConfGroup] = Field(default_factory=list)
|
||||||
|
modules: list[ConfModule] = Field(default_factory=list)
|
||||||
|
builtins: list[ConfBuiltin] = Field(default_factory=list)
|
||||||
@@ -28,6 +28,7 @@ class Settings(BaseSettings):
|
|||||||
default_user_agent: str = "sub-provider/0.2"
|
default_user_agent: str = "sub-provider/0.2"
|
||||||
|
|
||||||
sources_file: Path = CONFIG_DIR / "sources.yaml"
|
sources_file: Path = CONFIG_DIR / "sources.yaml"
|
||||||
|
conf_base_file: Path = CONFIG_DIR / "conf" / "base.conf"
|
||||||
rules_dir: Path = CONFIG_DIR / "rules"
|
rules_dir: Path = CONFIG_DIR / "rules"
|
||||||
bundle_cache_dir: Path = ROOT_DIR / "output" / "bundle-cache"
|
bundle_cache_dir: Path = ROOT_DIR / "output" / "bundle-cache"
|
||||||
fetch_cache_dir: Path = DATA_DIR / "fetch-cache"
|
fetch_cache_dir: Path = DATA_DIR / "fetch-cache"
|
||||||
|
|||||||
222
app/services/conf_loader.py
Normal file
222
app/services/conf_loader.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from app.conf_models import ConfBaseConfig, ConfBuiltin, ConfGroup, ConfModule, ConfSelector, ConfSource
|
||||||
|
|
||||||
|
_ENV_PATTERN = re.compile(r"\$\{([A-Z0-9_]+)\}")
|
||||||
|
_SCALAR_KEYS = {
|
||||||
|
"listen",
|
||||||
|
"output_dir",
|
||||||
|
"cache_dir",
|
||||||
|
"mode",
|
||||||
|
"allow_lan",
|
||||||
|
"log_level",
|
||||||
|
"ipv6",
|
||||||
|
"append_userinfo_header",
|
||||||
|
"userinfo_source_policy",
|
||||||
|
}
|
||||||
|
_BOOL_KEYS = {"allow_lan", "ipv6", "append_userinfo_header"}
|
||||||
|
_MULTI_KEYS = {"source", "selector", "group", "module", "builtin"}
|
||||||
|
_SOURCE_TYPES = {"url", "file", "inline", "base64"}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfConfigError(ValueError):
|
||||||
|
def __init__(self, path: Path, line_no: int, message: str) -> None:
|
||||||
|
self.path = path
|
||||||
|
self.line_no = line_no
|
||||||
|
self.message = message
|
||||||
|
super().__init__(f"{path}:{line_no}: {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_env(value: str) -> str:
|
||||||
|
return _ENV_PATTERN.sub(lambda match: os.getenv(match.group(1), ""), value)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_bool(value: str, *, path: Path, line_no: int, key: str) -> bool:
|
||||||
|
normalized = value.strip().lower()
|
||||||
|
mapping = {
|
||||||
|
"true": True,
|
||||||
|
"yes": True,
|
||||||
|
"1": True,
|
||||||
|
"false": False,
|
||||||
|
"no": False,
|
||||||
|
"0": False,
|
||||||
|
}
|
||||||
|
if normalized not in mapping:
|
||||||
|
raise ConfConfigError(path, line_no, f"invalid boolean for {key}: {value}")
|
||||||
|
return mapping[normalized]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_csv_fields(value: str, *, path: Path, line_no: int) -> list[str]:
|
||||||
|
try:
|
||||||
|
row = next(csv.reader([value], skipinitialspace=True))
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
raise ConfConfigError(path, line_no, f"invalid csv payload: {exc}") from exc
|
||||||
|
return [field.strip() for field in row]
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_named_records(records: list[Any], *, key: str, path: Path) -> None:
|
||||||
|
seen: dict[str, int] = {}
|
||||||
|
for record in records:
|
||||||
|
name = getattr(record, key)
|
||||||
|
line_no = getattr(record, "line_no")
|
||||||
|
previous_line = seen.get(name)
|
||||||
|
if previous_line is not None:
|
||||||
|
raise ConfConfigError(path, line_no, f"duplicate {record.__class__.__name__} key '{name}', first defined at line {previous_line}")
|
||||||
|
seen[name] = line_no
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_source(value: str, *, path: Path, line_no: int) -> ConfSource:
|
||||||
|
fields = _parse_csv_fields(_expand_env(value), path=path, line_no=line_no)
|
||||||
|
if len(fields) < 3:
|
||||||
|
raise ConfConfigError(path, line_no, "source requires at least 3 fields: key,type,value")
|
||||||
|
key, source_type, source_value, *extras = fields
|
||||||
|
if not key:
|
||||||
|
raise ConfConfigError(path, line_no, "source key cannot be empty")
|
||||||
|
if source_type not in _SOURCE_TYPES:
|
||||||
|
raise ConfConfigError(path, line_no, f"unsupported source type: {source_type}")
|
||||||
|
options: dict[str, str] = {}
|
||||||
|
enabled = True
|
||||||
|
cache_ttl: int | None = None
|
||||||
|
for item in extras:
|
||||||
|
if "=" not in item:
|
||||||
|
raise ConfConfigError(path, line_no, f"source option must be k=v: {item}")
|
||||||
|
option_key, option_value = [part.strip() for part in item.split("=", 1)]
|
||||||
|
if not option_key:
|
||||||
|
raise ConfConfigError(path, line_no, "source option key cannot be empty")
|
||||||
|
option_value = _expand_env(option_value)
|
||||||
|
if option_key == "enabled":
|
||||||
|
enabled = _parse_bool(option_value, path=path, line_no=line_no, key="source.enabled")
|
||||||
|
elif option_key == "cache_ttl":
|
||||||
|
try:
|
||||||
|
cache_ttl = int(option_value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ConfConfigError(path, line_no, f"invalid integer for source.cache_ttl: {option_value}") from exc
|
||||||
|
else:
|
||||||
|
options[option_key] = option_value
|
||||||
|
return ConfSource(
|
||||||
|
key=key,
|
||||||
|
source_type=source_type,
|
||||||
|
value=source_value,
|
||||||
|
enabled=enabled,
|
||||||
|
cache_ttl=cache_ttl,
|
||||||
|
options=options,
|
||||||
|
line_no=line_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_selector(value: str, *, path: Path, line_no: int) -> ConfSelector:
|
||||||
|
fields = _parse_csv_fields(value, path=path, line_no=line_no)
|
||||||
|
if len(fields) != 2:
|
||||||
|
raise ConfConfigError(path, line_no, "selector requires exactly 2 fields: key,regex")
|
||||||
|
key, regex = fields
|
||||||
|
if not key or not regex:
|
||||||
|
raise ConfConfigError(path, line_no, "selector key and regex cannot be empty")
|
||||||
|
return ConfSelector(key=key, regex=regex, line_no=line_no)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_group(value: str, *, path: Path, line_no: int) -> ConfGroup:
|
||||||
|
segments = [segment.strip() for segment in value.split("`")]
|
||||||
|
if len(segments) < 2:
|
||||||
|
raise ConfConfigError(path, line_no, "group requires at least name`type")
|
||||||
|
name, group_type, *tokens = segments
|
||||||
|
if not name or not group_type:
|
||||||
|
raise ConfConfigError(path, line_no, "group name and type cannot be empty")
|
||||||
|
return ConfGroup(name=name, group_type=group_type, tokens=tokens, raw=value.strip(), line_no=line_no)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_module(value: str, *, path: Path, line_no: int, config_dir: Path) -> ConfModule:
|
||||||
|
fields = _parse_csv_fields(value, path=path, line_no=line_no)
|
||||||
|
if len(fields) != 5:
|
||||||
|
raise ConfConfigError(path, line_no, "module requires exactly 5 fields: key,path,policy,order,enabled")
|
||||||
|
key, module_path, policy, order_text, enabled_text = fields
|
||||||
|
if not key or not module_path or not policy:
|
||||||
|
raise ConfConfigError(path, line_no, "module key, path, and policy cannot be empty")
|
||||||
|
try:
|
||||||
|
order = int(order_text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ConfConfigError(path, line_no, f"invalid integer for module.order: {order_text}") from exc
|
||||||
|
enabled = _parse_bool(enabled_text, path=path, line_no=line_no, key="module.enabled")
|
||||||
|
resolved_path = (config_dir / module_path).resolve()
|
||||||
|
if not resolved_path.is_file():
|
||||||
|
raise ConfConfigError(path, line_no, f"module path does not exist: {module_path}")
|
||||||
|
return ConfModule(key=key, path=module_path, policy=policy, order=order, enabled=enabled, line_no=line_no)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_builtin(value: str, *, path: Path, line_no: int) -> ConfBuiltin:
|
||||||
|
fields = _parse_csv_fields(value, path=path, line_no=line_no)
|
||||||
|
if len(fields) != 6:
|
||||||
|
raise ConfConfigError(path, line_no, "builtin requires exactly 6 fields: key,type,value,policy,order,enabled")
|
||||||
|
key, builtin_type, builtin_value, policy, order_text, enabled_text = fields
|
||||||
|
if builtin_type not in {"GEOIP", "FINAL"}:
|
||||||
|
raise ConfConfigError(path, line_no, f"unsupported builtin type: {builtin_type}")
|
||||||
|
if not key or not policy:
|
||||||
|
raise ConfConfigError(path, line_no, "builtin key and policy cannot be empty")
|
||||||
|
if builtin_type == "GEOIP" and not builtin_value:
|
||||||
|
raise ConfConfigError(path, line_no, "builtin GEOIP requires a value")
|
||||||
|
try:
|
||||||
|
order = int(order_text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ConfConfigError(path, line_no, f"invalid integer for builtin.order: {order_text}") from exc
|
||||||
|
enabled = _parse_bool(enabled_text, path=path, line_no=line_no, key="builtin.enabled")
|
||||||
|
return ConfBuiltin(
|
||||||
|
key=key,
|
||||||
|
builtin_type=builtin_type,
|
||||||
|
value=builtin_value,
|
||||||
|
policy=policy,
|
||||||
|
order=order,
|
||||||
|
enabled=enabled,
|
||||||
|
line_no=line_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_conf_base(path: Path) -> ConfBaseConfig:
|
||||||
|
config = ConfBaseConfig()
|
||||||
|
scalar_values: dict[str, Any] = {}
|
||||||
|
config_dir = path.resolve().parent
|
||||||
|
|
||||||
|
for line_no, raw_line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#") or line.startswith(";"):
|
||||||
|
continue
|
||||||
|
if "=" not in raw_line:
|
||||||
|
raise ConfConfigError(path, line_no, "expected key = value")
|
||||||
|
key, value = [part.strip() for part in raw_line.split("=", 1)]
|
||||||
|
if not key:
|
||||||
|
raise ConfConfigError(path, line_no, "key cannot be empty")
|
||||||
|
if key in _SCALAR_KEYS:
|
||||||
|
if key in scalar_values:
|
||||||
|
raise ConfConfigError(path, line_no, f"duplicate scalar key: {key}")
|
||||||
|
expanded_value = _expand_env(value)
|
||||||
|
if key in _BOOL_KEYS:
|
||||||
|
scalar_values[key] = _parse_bool(expanded_value, path=path, line_no=line_no, key=key)
|
||||||
|
else:
|
||||||
|
scalar_values[key] = expanded_value
|
||||||
|
continue
|
||||||
|
if key not in _MULTI_KEYS:
|
||||||
|
raise ConfConfigError(path, line_no, f"unknown directive: {key}")
|
||||||
|
if key == "source":
|
||||||
|
config.sources.append(_parse_source(value, path=path, line_no=line_no))
|
||||||
|
elif key == "selector":
|
||||||
|
config.selectors.append(_parse_selector(value, path=path, line_no=line_no))
|
||||||
|
elif key == "group":
|
||||||
|
config.groups.append(_parse_group(value, path=path, line_no=line_no))
|
||||||
|
elif key == "module":
|
||||||
|
config.modules.append(_parse_module(value, path=path, line_no=line_no, config_dir=config_dir))
|
||||||
|
elif key == "builtin":
|
||||||
|
config.builtins.append(_parse_builtin(value, path=path, line_no=line_no))
|
||||||
|
|
||||||
|
for key, parsed_value in scalar_values.items():
|
||||||
|
setattr(config, key, parsed_value)
|
||||||
|
|
||||||
|
_ensure_named_records(config.sources, key="key", path=path)
|
||||||
|
_ensure_named_records(config.selectors, key="key", path=path)
|
||||||
|
_ensure_named_records(config.groups, key="name", path=path)
|
||||||
|
_ensure_named_records(config.modules, key="key", path=path)
|
||||||
|
_ensure_named_records(config.builtins, key="key", path=path)
|
||||||
|
return config
|
||||||
29
config/conf/base.conf
Normal file
29
config/conf/base.conf
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
listen = 0.0.0.0:3000
|
||||||
|
output_dir = ./output
|
||||||
|
cache_dir = ./data/cache
|
||||||
|
mode = rule
|
||||||
|
allow_lan = true
|
||||||
|
log_level = info
|
||||||
|
ipv6 = true
|
||||||
|
append_userinfo_header = true
|
||||||
|
userinfo_source_policy = first_enabled_source
|
||||||
|
|
||||||
|
source = airport-a,url,${AIRPORT_A_URL},enabled=true,cache_ttl=1800
|
||||||
|
source = airport-b,url,${AIRPORT_B_URL},enabled=true,cache_ttl=1800
|
||||||
|
|
||||||
|
selector = all,.*
|
||||||
|
selector = hk,(?i)(港|hk|hong kong|hongkong)
|
||||||
|
selector = us,(?i)(美|us|united states)
|
||||||
|
|
||||||
|
group = 🚀 节点选择`select`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇺🇲 美国节点`[]DIRECT
|
||||||
|
group = ♻️ 自动选择`url-test`@all`https://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇭🇰 香港节点`url-test`@hk`https://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇺🇲 美国节点`url-test`@us`https://www.gstatic.com/generate_204`300,,150
|
||||||
|
group = 🐟 漏网之鱼`select`[]DIRECT`[]🚀 节点选择
|
||||||
|
|
||||||
|
module = local-network,../rules/acl4ssr/LocalAreaNetwork.yaml,🎯 全球直连,10,true
|
||||||
|
module = apple,../rules/acl4ssr/Apple.yaml,🍎 苹果服务,80,true
|
||||||
|
module = openai,../rules/acl4ssr/OpenAi.yaml,💬 Ai平台,100,true
|
||||||
|
|
||||||
|
builtin = geoip_cn,GEOIP,CN,🎯 全球直连,9000,true
|
||||||
|
builtin = final,FINAL,,🐟 漏网之鱼,9999,true
|
||||||
809
docs/ini改造方案.md
Normal file
809
docs/ini改造方案.md
Normal file
@@ -0,0 +1,809 @@
|
|||||||
|
可以。下面这份就是一版 **“类 ACL4SSR 风格 INI/CONF 配置整体说明”**,你可以直接丢给 Codex。
|
||||||
|
它刻意 **不引入 YAML**,只保留高密度、行导向、声明式的写法。你现在贴的配置本身已经在用 `[custom]`、`ruleset=`、`custom_proxy_group=`、`enable_rule_generator=true`、`overwrite_original_rules=true` 这套思路;subconverter 现有 ACL4SSR 相关配置和外部配置 gist 里也能看到同类结构,甚至有把公共规则和公共分组拆出去再导入的写法。 ([GitHub][1])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# sub-provider 类 ACL4SSR 风格 INI 配置说明(草案)
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
|
||||||
|
将 sub-provider 的配置设计为一种 **ACL4SSR-like 的高密度 INI/CONF DSL**,用于:
|
||||||
|
|
||||||
|
* 管理订阅源
|
||||||
|
* 定义节点选择器
|
||||||
|
* 定义代理组
|
||||||
|
* 注册规则模块
|
||||||
|
* 选择输出 profile
|
||||||
|
* 生成完整 Clash/Mihomo YAML
|
||||||
|
|
||||||
|
这里的重点不是兼容标准 INI 解析器,而是:
|
||||||
|
|
||||||
|
> **保留 INI 的可读感 + ACL4SSR 的高密度声明风格 + 方便 Python 自定义解析**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 设计原则
|
||||||
|
|
||||||
|
### 2.1 保留 ACL4SSR 的使用习惯
|
||||||
|
|
||||||
|
沿用这些核心心智模型:
|
||||||
|
|
||||||
|
* `ruleset = 规则模块 -> 目标策略组`
|
||||||
|
* `group = 策略组定义`
|
||||||
|
* `selector = 节点筛选器`
|
||||||
|
* `source = 订阅源`
|
||||||
|
* `profile = 输出方案`
|
||||||
|
|
||||||
|
### 2.2 高密度、行导向
|
||||||
|
|
||||||
|
每一条配置尽量一行表达一个对象,方便:
|
||||||
|
|
||||||
|
* 快速编辑
|
||||||
|
* 批量复制
|
||||||
|
* diff
|
||||||
|
* 注释开关
|
||||||
|
* 手工维护
|
||||||
|
|
||||||
|
### 2.3 规则正文与配置分离
|
||||||
|
|
||||||
|
* `*.list` 文件只保存规则主体
|
||||||
|
* 目标策略组由配置指定
|
||||||
|
* 生成器负责最终拼接
|
||||||
|
|
||||||
|
### 2.4 不追求严格标准 INI
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
* 标准 INI 不擅长重复 key
|
||||||
|
* 不擅长表达列表对象
|
||||||
|
* 不擅长表达高密度分组语法
|
||||||
|
|
||||||
|
因此这里定义的是:
|
||||||
|
|
||||||
|
> **INI-like / CONF-like 自定义配置格式**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 文件组织
|
||||||
|
|
||||||
|
推荐结构:
|
||||||
|
|
||||||
|
```text
|
||||||
|
config/
|
||||||
|
base.conf
|
||||||
|
profiles/
|
||||||
|
default.conf
|
||||||
|
lite.conf
|
||||||
|
media.conf
|
||||||
|
|
||||||
|
rules/
|
||||||
|
modules/
|
||||||
|
Riglen.list
|
||||||
|
LocalAreaNetwork.list
|
||||||
|
UnBan.list
|
||||||
|
BanAD.list
|
||||||
|
BanProgramAD.list
|
||||||
|
Google.list
|
||||||
|
GoogleCN.list
|
||||||
|
SteamCN.list
|
||||||
|
Bing.list
|
||||||
|
OneDrive.list
|
||||||
|
Microsoft.list
|
||||||
|
Apple.list
|
||||||
|
Telegram.list
|
||||||
|
AI.list
|
||||||
|
OpenAi.list
|
||||||
|
YouTube.list
|
||||||
|
Netflix.list
|
||||||
|
ProxyMedia.list
|
||||||
|
ProxyGFWlist.list
|
||||||
|
Pt.list
|
||||||
|
ChinaDomain.list
|
||||||
|
ChinaCompanyIp.list
|
||||||
|
Download.list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
* `base.conf`:全局配置、源、选择器、组定义、模块注册、内置规则
|
||||||
|
* `profiles/*.conf`:不同输出方案
|
||||||
|
* `rules/modules/*.list`:规则正文
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 基础语法
|
||||||
|
|
||||||
|
## 4.1 注释
|
||||||
|
|
||||||
|
支持两种注释:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
; 这是注释
|
||||||
|
# 这也是注释
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4.2 空行
|
||||||
|
|
||||||
|
空行忽略。
|
||||||
|
|
||||||
|
## 4.3 键值形式
|
||||||
|
|
||||||
|
基本格式:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
key = value
|
||||||
|
```
|
||||||
|
|
||||||
|
左右空格允许存在,解析时应 trim。
|
||||||
|
|
||||||
|
## 4.4 重复 key
|
||||||
|
|
||||||
|
允许重复 key。
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
source = ...
|
||||||
|
source = ...
|
||||||
|
selector = ...
|
||||||
|
selector = ...
|
||||||
|
group = ...
|
||||||
|
group = ...
|
||||||
|
module = ...
|
||||||
|
module = ...
|
||||||
|
```
|
||||||
|
|
||||||
|
解析器应将这类 key 视为“多条记录”,而不是覆盖。
|
||||||
|
|
||||||
|
## 4.5 大小写
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
* 指令名小写
|
||||||
|
* 值区分大小写
|
||||||
|
* 策略组名、规则内容、正则表达式保持原样
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 支持的指令
|
||||||
|
|
||||||
|
本方案定义以下核心指令:
|
||||||
|
|
||||||
|
* `listen`
|
||||||
|
* `output_dir`
|
||||||
|
* `cache_dir`
|
||||||
|
* `mode`
|
||||||
|
* `allow_lan`
|
||||||
|
* `log_level`
|
||||||
|
* `ipv6`
|
||||||
|
* `append_userinfo_header`
|
||||||
|
* `userinfo_source_policy`
|
||||||
|
|
||||||
|
以及对象型指令:
|
||||||
|
|
||||||
|
* `source =`
|
||||||
|
* `selector =`
|
||||||
|
* `group =`
|
||||||
|
* `module =`
|
||||||
|
* `builtin =`
|
||||||
|
|
||||||
|
profile 内支持:
|
||||||
|
|
||||||
|
* `name =`
|
||||||
|
* `enabled =`
|
||||||
|
* `sources =`
|
||||||
|
* `include_modules =`
|
||||||
|
* `exclude_modules =`
|
||||||
|
* `include_builtins =`
|
||||||
|
* `override_policy =`
|
||||||
|
* `prepend_rule =`
|
||||||
|
* `append_rule =`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. base.conf 说明
|
||||||
|
|
||||||
|
## 6.1 全局标量配置
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
listen = 0.0.0.0:3000
|
||||||
|
output_dir = ./output
|
||||||
|
cache_dir = ./data/cache
|
||||||
|
mode = rule
|
||||||
|
allow_lan = true
|
||||||
|
log_level = info
|
||||||
|
ipv6 = false
|
||||||
|
append_userinfo_header = true
|
||||||
|
userinfo_source_policy = first_enabled_source
|
||||||
|
```
|
||||||
|
|
||||||
|
### 含义
|
||||||
|
|
||||||
|
* `listen`:监听地址
|
||||||
|
* `output_dir`:生成结果输出目录
|
||||||
|
* `cache_dir`:缓存目录
|
||||||
|
* `mode`:Clash 模式,通常为 `rule`
|
||||||
|
* `allow_lan`:是否允许局域网访问
|
||||||
|
* `log_level`:日志级别
|
||||||
|
* `ipv6`:是否开启 IPv6
|
||||||
|
* `append_userinfo_header`:是否透传订阅流量头
|
||||||
|
* `userinfo_source_policy`:多源时如何选取订阅信息头
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.2 source 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
定义订阅输入源。
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
source = key,type,value,enabled=true,cache_ttl=1800
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
source = airport_main,url,https://example.com/sub?token=xxxx,enabled=true,cache_ttl=1800
|
||||||
|
source = local_links,file,./data/local_links.txt,enabled=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
|
||||||
|
* 第 1 列:`key`
|
||||||
|
* 第 2 列:`type`
|
||||||
|
* 第 3 列:`value`
|
||||||
|
* 后续:可选命名参数
|
||||||
|
|
||||||
|
### 支持类型
|
||||||
|
|
||||||
|
* `url`:远程订阅链接
|
||||||
|
* `file`:本地文件
|
||||||
|
* `inline`:内联文本
|
||||||
|
* `base64`:base64 内容
|
||||||
|
|
||||||
|
### 建议解析规则
|
||||||
|
|
||||||
|
前三列固定,后续按 `k=v` 解析。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.3 selector 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
定义节点筛选器,供代理组复用。
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
selector = key,regex
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
selector = all,.*
|
||||||
|
selector = hk,(港|HK|hk|Hong Kong|HongKong|hongkong)
|
||||||
|
selector = jp,(日本|东京|大阪|JP|Japan)
|
||||||
|
selector = us,(美|洛杉矶|西雅图|US|United States)
|
||||||
|
selector = netflix,(NF|奈飞|解锁|Netflix|NETFLIX|Media)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 含义
|
||||||
|
|
||||||
|
* `key`:选择器名
|
||||||
|
* `regex`:匹配节点名的正则表达式
|
||||||
|
|
||||||
|
### 约定
|
||||||
|
|
||||||
|
后续在 `group` 指令中通过 `@selector_key` 引用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.4 group 指令
|
||||||
|
|
||||||
|
这是整个 DSL 的核心。
|
||||||
|
它保留 ACL4SSR 风格的高密度分组写法。你的示例里已经大量使用 `custom_proxy_group=` 这种反引号分隔格式。 现有 subconverter 相关 ACL4SSR 配置里也是同类写法。([GitHub][1])
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 组名`类型`参数1`参数2`参数3...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 成员约定
|
||||||
|
|
||||||
|
* `[]组名`:引用已有组
|
||||||
|
* `[]DIRECT`:内置 DIRECT
|
||||||
|
* `[]REJECT`:内置 REJECT
|
||||||
|
* `@selector_key`:引用 selector 动态筛节点
|
||||||
|
* `.*`:兼容保留写法,可映射为全部节点
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 🚀 节点选择`select`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇯🇵 日本节点`[]🚀 手动切换`[]DIRECT
|
||||||
|
group = 🚀 手动切换`select`@all
|
||||||
|
group = ♻️ 自动选择`url-test`@all`http://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇭🇰 香港节点`url-test`@hk`http://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇯🇵 日本节点`url-test`@jp`http://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🎥 奈飞节点`select`@netflix
|
||||||
|
group = 🐟 漏网之鱼`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解析规则建议
|
||||||
|
|
||||||
|
#### `select`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 组名`select`成员1`成员2`成员3...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `url-test`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 组名`url-test`@selector`测试URL`间隔,,容差
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `fallback`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 组名`fallback`@selector`测试URL`间隔,,容差
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `load-balance`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = 组名`load-balance`@selector`测试URL`间隔,,容差
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设计建议
|
||||||
|
|
||||||
|
不要再继续沿用 `custom_proxy_group=` 这个旧名字。
|
||||||
|
内部 DSL 里直接统一为:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
group = ...
|
||||||
|
```
|
||||||
|
|
||||||
|
这样更短,也更像你自己的项目语法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.5 module 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
注册规则模块。
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
module = key,path,policy,order,enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
module = riglen,./rules/modules/Riglen.list,🚀 节点选择,10,true
|
||||||
|
module = lan,./rules/modules/LocalAreaNetwork.list,🎯 全球直连,20,true
|
||||||
|
module = banad,./rules/modules/BanAD.list,🛑 广告拦截,40,true
|
||||||
|
module = apple,./rules/modules/Apple.list,🍎 苹果服务,80,true
|
||||||
|
module = ai,./rules/modules/AI.list,💬 Ai平台,100,true
|
||||||
|
module = openai,./rules/modules/OpenAi.list,💬 Ai平台,101,true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
|
||||||
|
* `key`:模块唯一标识
|
||||||
|
* `path`:规则文件路径
|
||||||
|
* `policy`:默认策略组
|
||||||
|
* `order`:排序
|
||||||
|
* `enabled`:默认是否启用
|
||||||
|
|
||||||
|
### 规则文件约定
|
||||||
|
|
||||||
|
规则文件只保存规则主体,不含目标策略组。
|
||||||
|
|
||||||
|
例如 `OpenAi.list`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
DOMAIN-SUFFIX,openai.com
|
||||||
|
DOMAIN-SUFFIX,chatgpt.com
|
||||||
|
DOMAIN-SUFFIX,oaistatic.com
|
||||||
|
```
|
||||||
|
|
||||||
|
生成时补成:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
DOMAIN-SUFFIX,openai.com,💬 Ai平台
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.6 builtin 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
定义不来自 `.list` 文件的内置规则。
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
builtin = key,type,value,policy,order,enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
builtin = geoip_cn,GEOIP,CN,🎯 全球直连,9000,true
|
||||||
|
builtin = final,FINAL,,🐟 漏网之鱼,9999,true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 映射规则
|
||||||
|
|
||||||
|
* `GEOIP + CN + 🎯 全球直连`
|
||||||
|
-> `GEOIP,CN,🎯 全球直连`
|
||||||
|
* `FINAL + 🐟 漏网之鱼`
|
||||||
|
-> `MATCH,🐟 漏网之鱼`
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
这里保留了 ACL4SSR 配置里 `[]GEOIP,CN`、`[]FINAL` 这种内置规则思想,只是换成更适合你项目的数据化声明。你的现有配置里已经有这两类用法。 ([GitHub][1])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. profile 配置说明
|
||||||
|
|
||||||
|
每个 profile 一个文件,放在 `config/profiles/` 下。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7.1 基本字段
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
name = 默认完整配置
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
sources = airport_main
|
||||||
|
include_modules = riglen,lan,unban,banad,apple,telegram,ai,openai
|
||||||
|
exclude_modules =
|
||||||
|
include_builtins = geoip_cn,final
|
||||||
|
```
|
||||||
|
|
||||||
|
### 含义
|
||||||
|
|
||||||
|
* `name`:profile 名称
|
||||||
|
* `enabled`:是否启用
|
||||||
|
* `sources`:本 profile 使用哪些订阅源
|
||||||
|
* `include_modules`:启用哪些模块
|
||||||
|
* `exclude_modules`:从启用列表中排除哪些模块
|
||||||
|
* `include_builtins`:启用哪些内置规则
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7.2 override_policy 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
覆盖模块默认策略组。
|
||||||
|
|
||||||
|
### 格式
|
||||||
|
|
||||||
|
```ini
|
||||||
|
override_policy = module_key,new_policy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
override_policy = apple,🚀 节点选择
|
||||||
|
override_policy = ai,💬 Ai平台
|
||||||
|
```
|
||||||
|
|
||||||
|
### 含义
|
||||||
|
|
||||||
|
例如某模块默认是 `🍎 苹果服务`,某 profile 想改成 `🚀 节点选择`,就在 profile 里覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7.3 prepend_rule / append_rule 指令
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
给特定 profile 注入自定义规则。
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
prepend_rule = DOMAIN-SUFFIX,internal.example.com,DIRECT
|
||||||
|
append_rule = DOMAIN-SUFFIX,test.example.com,🚀 节点选择
|
||||||
|
```
|
||||||
|
|
||||||
|
### 含义
|
||||||
|
|
||||||
|
* `prepend_rule`:插在 rules 最前
|
||||||
|
* `append_rule`:插在 builtin 之前或之后,由实现约定
|
||||||
|
|
||||||
|
### 建议
|
||||||
|
|
||||||
|
`MATCH` / `FINAL` 仍应最后输出。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 解析器行为规范
|
||||||
|
|
||||||
|
## 8.1 base.conf
|
||||||
|
|
||||||
|
解析器应支持:
|
||||||
|
|
||||||
|
* 普通标量键值
|
||||||
|
* 多条 `source`
|
||||||
|
* 多条 `selector`
|
||||||
|
* 多条 `group`
|
||||||
|
* 多条 `module`
|
||||||
|
* 多条 `builtin`
|
||||||
|
|
||||||
|
## 8.2 profile.conf
|
||||||
|
|
||||||
|
解析器应支持:
|
||||||
|
|
||||||
|
* 普通标量键值
|
||||||
|
* 多条 `override_policy`
|
||||||
|
* 多条 `prepend_rule`
|
||||||
|
* 多条 `append_rule`
|
||||||
|
|
||||||
|
## 8.3 字段 trim
|
||||||
|
|
||||||
|
建议默认:
|
||||||
|
|
||||||
|
* 去掉左右空白
|
||||||
|
* 不改动组名内部空格
|
||||||
|
* 不改动正则原文
|
||||||
|
|
||||||
|
## 8.4 布尔值
|
||||||
|
|
||||||
|
支持:
|
||||||
|
|
||||||
|
* `true/false`
|
||||||
|
* `yes/no`
|
||||||
|
* `1/0`
|
||||||
|
|
||||||
|
内部统一成布尔值。
|
||||||
|
|
||||||
|
## 8.5 列表字段
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
sources = airport_main,local_links
|
||||||
|
include_modules = apple,ai,openai
|
||||||
|
```
|
||||||
|
|
||||||
|
按逗号切分,trim 后转数组。
|
||||||
|
|
||||||
|
## 8.6 module/source 的 CSV 解析
|
||||||
|
|
||||||
|
建议用 Python `csv` 模块解析逗号分隔,而不是简单 `split(",")`,避免后续扩展时出问题。
|
||||||
|
|
||||||
|
## 8.7 group 的反引号解析
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
1. 去掉前缀 `group =`
|
||||||
|
2. 按反引号 `` ` `` 分段
|
||||||
|
3. 第一段为组名
|
||||||
|
4. 第二段为类型
|
||||||
|
5. 后续段按组类型解释
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 生成流程规范
|
||||||
|
|
||||||
|
生成器处理顺序建议如下:
|
||||||
|
|
||||||
|
### 第 1 步:加载 base.conf
|
||||||
|
|
||||||
|
构建:
|
||||||
|
|
||||||
|
* 源表
|
||||||
|
* selector 表
|
||||||
|
* group 定义表
|
||||||
|
* module 注册表
|
||||||
|
* builtin 注册表
|
||||||
|
|
||||||
|
### 第 2 步:加载指定 profile.conf
|
||||||
|
|
||||||
|
得到:
|
||||||
|
|
||||||
|
* 使用哪些源
|
||||||
|
* 启用哪些模块
|
||||||
|
* 启用哪些 builtin
|
||||||
|
* policy override
|
||||||
|
* prepend/append 规则
|
||||||
|
|
||||||
|
### 第 3 步:拉取并解析节点
|
||||||
|
|
||||||
|
* 拉 URL 订阅
|
||||||
|
* 读本地文件
|
||||||
|
* 统一解析为节点对象
|
||||||
|
* 去重 / 重命名 / 过滤
|
||||||
|
|
||||||
|
### 第 4 步:按 selector 构建节点集合
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
* `hk` -> 匹配香港节点
|
||||||
|
* `us` -> 匹配美国节点
|
||||||
|
* `all` -> 全部节点
|
||||||
|
|
||||||
|
### 第 5 步:构建 proxy-groups
|
||||||
|
|
||||||
|
解析 `group = ...` 指令,生成最终组定义。
|
||||||
|
|
||||||
|
### 第 6 步:构建 rules
|
||||||
|
|
||||||
|
1. 按模块顺序加载 `.list`
|
||||||
|
2. 对每行规则补上目标 policy
|
||||||
|
3. 应用 `override_policy`
|
||||||
|
4. 加入 `prepend_rule`
|
||||||
|
5. 加入 builtin
|
||||||
|
6. 加入 `append_rule`
|
||||||
|
7. 确保 `FINAL/MATCH` 最后
|
||||||
|
|
||||||
|
### 第 7 步:渲染完整 YAML
|
||||||
|
|
||||||
|
这里是生成器内部输出,不需要在配置层暴露 YAML 结构给用户。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 错误处理建议
|
||||||
|
|
||||||
|
以下情况应报错并指出行号:
|
||||||
|
|
||||||
|
* 未知指令
|
||||||
|
* `group` 语法不合法
|
||||||
|
* `module` 路径不存在
|
||||||
|
* profile 引用了不存在的 `source`
|
||||||
|
* profile 引用了不存在的 `module`
|
||||||
|
* `override_policy` 指向未知模块
|
||||||
|
* `@selector_key` 未定义
|
||||||
|
* builtin 类型不支持
|
||||||
|
|
||||||
|
以下情况可仅 warning:
|
||||||
|
|
||||||
|
* selector 匹配不到节点
|
||||||
|
* group 最终成员为空
|
||||||
|
* 模块文件为空
|
||||||
|
* 规则重复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 兼容性策略建议
|
||||||
|
|
||||||
|
## 11.1 兼容旧 ACL4SSR 心智模型
|
||||||
|
|
||||||
|
虽然这是你自己的 DSL,但建议:
|
||||||
|
|
||||||
|
* 注释继续支持 `;`
|
||||||
|
* 保留 `ruleset -> policy` 的思想
|
||||||
|
* 保留高密度 `group` 行语法
|
||||||
|
* 保留 `GEOIP` / `FINAL` 的 builtin 概念
|
||||||
|
|
||||||
|
## 11.2 不必兼容旧关键字原样
|
||||||
|
|
||||||
|
内部不建议继续沿用:
|
||||||
|
|
||||||
|
* `ruleset=`
|
||||||
|
* `custom_proxy_group=`
|
||||||
|
|
||||||
|
建议统一改成更短的:
|
||||||
|
|
||||||
|
* `module =`
|
||||||
|
* `group =`
|
||||||
|
|
||||||
|
这样语义更清晰:
|
||||||
|
|
||||||
|
* `module` 是规则模块注册
|
||||||
|
* `group` 是代理组定义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 最小示例
|
||||||
|
|
||||||
|
## 12.1 base.conf
|
||||||
|
|
||||||
|
```ini
|
||||||
|
listen = 0.0.0.0:3000
|
||||||
|
mode = rule
|
||||||
|
allow_lan = true
|
||||||
|
log_level = info
|
||||||
|
|
||||||
|
source = airport_main,url,https://example.com/sub?token=xxxx,enabled=true,cache_ttl=1800
|
||||||
|
|
||||||
|
selector = all,.*
|
||||||
|
selector = hk,(港|HK|hk|Hong Kong|HongKong|hongkong)
|
||||||
|
selector = us,(美|US|United States)
|
||||||
|
|
||||||
|
group = 🚀 节点选择`select`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇺🇲 美国节点`[]DIRECT
|
||||||
|
group = ♻️ 自动选择`url-test`@all`http://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇭🇰 香港节点`url-test`@hk`http://www.gstatic.com/generate_204`300,,50
|
||||||
|
group = 🇺🇲 美国节点`url-test`@us`http://www.gstatic.com/generate_204`300,,150
|
||||||
|
group = 🐟 漏网之鱼`select`[]DIRECT`[]🚀 节点选择
|
||||||
|
|
||||||
|
module = apple,./rules/modules/Apple.list,🍎 苹果服务,80,true
|
||||||
|
module = openai,./rules/modules/OpenAi.list,💬 Ai平台,100,true
|
||||||
|
|
||||||
|
builtin = geoip_cn,GEOIP,CN,🎯 全球直连,9000,true
|
||||||
|
builtin = final,FINAL,,🐟 漏网之鱼,9999,true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12.2 profiles/default.conf
|
||||||
|
|
||||||
|
```ini
|
||||||
|
name = 默认完整配置
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
sources = airport_main
|
||||||
|
include_modules = apple,openai
|
||||||
|
exclude_modules =
|
||||||
|
include_builtins = geoip_cn,final
|
||||||
|
|
||||||
|
override_policy = apple,🚀 节点选择
|
||||||
|
prepend_rule = DOMAIN-SUFFIX,internal.example.com,DIRECT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 给 Codex 的实现要求
|
||||||
|
|
||||||
|
你可以直接把下面这段交给 Codex:
|
||||||
|
|
||||||
|
```md
|
||||||
|
请为 sub-provider 设计并实现一套类 ACL4SSR 风格的 INI/CONF 配置系统,不要引入 YAML 配置层。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
- 使用高密度、行导向、声明式配置
|
||||||
|
- 保留 ACL4SSR 的使用习惯,但不要求 100% 兼容其原始关键字
|
||||||
|
- 规则正文继续使用 rules/modules/*.list
|
||||||
|
- 配置文件负责声明:
|
||||||
|
- source
|
||||||
|
- selector
|
||||||
|
- group
|
||||||
|
- module
|
||||||
|
- builtin
|
||||||
|
- profile
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. base.conf 负责全局配置、source、selector、group、module、builtin。
|
||||||
|
2. profiles/*.conf 负责具体输出 profile。
|
||||||
|
3. group 使用反引号分隔的 ACL4SSR-like 语法。
|
||||||
|
4. module 指向规则文件路径和默认策略组。
|
||||||
|
5. builtin 用于表达 GEOIP、FINAL 这类内置规则。
|
||||||
|
6. 生成器读取这些 conf 文件后,生成完整 Clash/Mihomo YAML。
|
||||||
|
7. 不要接数据库,不要做面板。
|
||||||
|
8. 解析器要能给出清晰的行号错误提示。
|
||||||
|
9. profile 支持 override_policy、prepend_rule、append_rule。
|
||||||
|
10. 先实现最小可用版本,再逐步扩展。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 最终结论
|
||||||
|
|
||||||
|
你现在最适合的路线,就是:
|
||||||
|
|
||||||
|
> **规则正文继续放 `.list`**
|
||||||
|
> **配置层改成类 ACL4SSR 风格的 `.conf`**
|
||||||
|
> **Python 写一个轻量 parser + generator**
|
||||||
|
> **最终输出完整 YAML**
|
||||||
|
|
||||||
|
这条路最顺,也最符合你现在的使用习惯。
|
||||||
|
|
||||||
|
[1]: https://github.com/tindy2013/subconverter/blob/master/base/config/ACL4SSR_NoMicrosoft.ini "subconverter/base/config/ACL4SSR_NoMicrosoft.ini at master · tindy2013/subconverter · GitHub"
|
||||||
Reference in New Issue
Block a user