429 lines
12 KiB
Go
429 lines
12 KiB
Go
package coremain
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"go.uber.org/zap"
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
// DomainRule 域名规则配置
|
||
type DomainRule struct {
|
||
Name string `json:"name"` // 规则名称
|
||
Description string `json:"description"` // 规则描述
|
||
DomainFile string `json:"domain_file"` // 域名文件路径
|
||
DNSStrategy string `json:"dns_strategy"` // DNS策略:china-dns/overseas-dns/smart-fallback
|
||
EnableMikroTik bool `json:"enable_mikrotik"` // 是否启用MikroTik推送
|
||
MikroTikConfig MikroTikConfig `json:"mikrotik_config"` // MikroTik配置
|
||
Enabled bool `json:"enabled"` // 是否启用
|
||
}
|
||
|
||
// MikroTikConfig MikroTik路由器配置
|
||
type MikroTikConfig struct {
|
||
Host string `json:"host"` // 路由器地址
|
||
Port int `json:"port"` // API端口
|
||
Username string `json:"username"` // 用户名
|
||
Password string `json:"password"` // 密码
|
||
AddressList string `json:"address_list"` // 地址列表名称
|
||
Mask int `json:"mask"` // IP掩码
|
||
MaxIPs int `json:"max_ips"` // 最大IP数
|
||
CacheTTL int `json:"cache_ttl"` // 缓存时间(秒)
|
||
TimeoutAddr int `json:"timeout_addr"` // 地址超时(秒)
|
||
Comment string `json:"comment"` // 备注
|
||
}
|
||
|
||
// ConfigBuilder 高级配置构建器
|
||
type ConfigBuilder struct {
|
||
baseConfig *Config
|
||
logger *zap.Logger
|
||
rulesDir string
|
||
}
|
||
|
||
// NewConfigBuilder 创建配置构建器
|
||
func NewConfigBuilder(baseConfig *Config, logger *zap.Logger) *ConfigBuilder {
|
||
return &ConfigBuilder{
|
||
baseConfig: baseConfig,
|
||
logger: logger,
|
||
rulesDir: "config.d/rules",
|
||
}
|
||
}
|
||
|
||
// AddDomainRule 添加域名规则
|
||
func (b *ConfigBuilder) AddDomainRule(rule DomainRule) error {
|
||
if rule.Name == "" {
|
||
return fmt.Errorf("规则名称不能为空")
|
||
}
|
||
|
||
if rule.DomainFile == "" {
|
||
return fmt.Errorf("域名文件路径不能为空")
|
||
}
|
||
|
||
if rule.DNSStrategy == "" {
|
||
rule.DNSStrategy = "smart-fallback" // 默认使用智能防污染
|
||
}
|
||
|
||
// 生成规则配置文件
|
||
ruleConfig := b.generateRuleConfig(rule)
|
||
ruleFileName := rule.Name + ".yaml"
|
||
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 保存规则文件
|
||
if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil {
|
||
return fmt.Errorf("保存规则文件失败: %w", err)
|
||
}
|
||
|
||
// 将规则添加到主配置的include中
|
||
b.addRuleToIncludes(ruleFileName)
|
||
|
||
b.logger.Info("域名规则添加成功",
|
||
zap.String("rule_name", rule.Name),
|
||
zap.String("file_path", ruleFilePath))
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateDomainRule 更新域名规则
|
||
func (b *ConfigBuilder) UpdateDomainRule(ruleName string, rule DomainRule) error {
|
||
ruleFileName := ruleName + ".yaml"
|
||
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 检查规则文件是否存在
|
||
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
|
||
return fmt.Errorf("规则文件不存在: %s", ruleFilePath)
|
||
}
|
||
|
||
// 生成新的规则配置
|
||
ruleConfig := b.generateRuleConfig(rule)
|
||
|
||
// 保存更新后的规则文件
|
||
if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil {
|
||
return fmt.Errorf("更新规则文件失败: %w", err)
|
||
}
|
||
|
||
b.logger.Info("域名规则更新成功",
|
||
zap.String("rule_name", ruleName),
|
||
zap.String("file_path", ruleFilePath))
|
||
|
||
return nil
|
||
}
|
||
|
||
// DeleteDomainRule 删除域名规则
|
||
func (b *ConfigBuilder) DeleteDomainRule(ruleName string) error {
|
||
ruleFileName := ruleName + ".yaml"
|
||
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 检查规则文件是否存在
|
||
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
|
||
return fmt.Errorf("规则文件不存在: %s", ruleFilePath)
|
||
}
|
||
|
||
// 删除规则文件
|
||
if err := os.Remove(ruleFilePath); err != nil {
|
||
return fmt.Errorf("删除规则文件失败: %w", err)
|
||
}
|
||
|
||
// 从主配置的include中移除
|
||
b.removeRuleFromIncludes(ruleFileName)
|
||
|
||
b.logger.Info("域名规则删除成功",
|
||
zap.String("rule_name", ruleName),
|
||
zap.String("file_path", ruleFilePath))
|
||
|
||
return nil
|
||
}
|
||
|
||
// generateRuleConfig 生成规则配置文件内容
|
||
func (b *ConfigBuilder) generateRuleConfig(rule DomainRule) *Config {
|
||
config := &Config{
|
||
Plugins: []PluginConfig{},
|
||
}
|
||
|
||
// 1. 创建域名集合插件
|
||
domainSetTag := "domains_" + rule.Name
|
||
domainSetPlugin := PluginConfig{
|
||
Tag: domainSetTag,
|
||
Type: "domain_set",
|
||
Args: map[string]interface{}{
|
||
"files": []string{rule.DomainFile},
|
||
},
|
||
}
|
||
config.Plugins = append(config.Plugins, domainSetPlugin)
|
||
|
||
// 2. 创建规则执行插件
|
||
ruleTag := "rule_" + rule.Name
|
||
var ruleExec []map[string]interface{}
|
||
|
||
if rule.Enabled {
|
||
ruleExec = []map[string]interface{}{
|
||
{
|
||
"matches": "qname $" + domainSetTag,
|
||
"exec": "$" + rule.DNSStrategy,
|
||
},
|
||
}
|
||
}
|
||
|
||
rulePlugin := PluginConfig{
|
||
Tag: ruleTag,
|
||
Type: "sequence",
|
||
Args: map[string]interface{}{
|
||
"exec": ruleExec,
|
||
},
|
||
}
|
||
config.Plugins = append(config.Plugins, rulePlugin)
|
||
|
||
// 3. 如果启用MikroTik,创建MikroTik推送插件
|
||
if rule.EnableMikroTik {
|
||
mikrotikTag := "mikrotik_" + rule.Name
|
||
mikrotikPlugin := PluginConfig{
|
||
Tag: mikrotikTag,
|
||
Type: "mikrotik_addresslist",
|
||
Args: map[string]interface{}{
|
||
"host": rule.MikroTikConfig.Host,
|
||
"port": rule.MikroTikConfig.Port,
|
||
"username": rule.MikroTikConfig.Username,
|
||
"password": rule.MikroTikConfig.Password,
|
||
"address_list": b.getMikroTikAddressList(rule),
|
||
"mask": rule.MikroTikConfig.Mask,
|
||
"max_ips": rule.MikroTikConfig.MaxIPs,
|
||
"cache_ttl": rule.MikroTikConfig.CacheTTL,
|
||
"timeout_addr": rule.MikroTikConfig.TimeoutAddr,
|
||
"comment": b.getMikroTikComment(rule),
|
||
},
|
||
}
|
||
config.Plugins = append(config.Plugins, mikrotikPlugin)
|
||
|
||
// 在规则执行后添加MikroTik推送
|
||
rulePlugin.Args = map[string]interface{}{
|
||
"exec": append(ruleExec, map[string]interface{}{
|
||
"matches": "has_resp",
|
||
"exec": "$" + mikrotikTag,
|
||
}),
|
||
}
|
||
}
|
||
|
||
return config
|
||
}
|
||
|
||
// getMikroTikAddressList 获取MikroTik地址列表名称
|
||
func (b *ConfigBuilder) getMikroTikAddressList(rule DomainRule) string {
|
||
if rule.MikroTikConfig.AddressList != "" {
|
||
return rule.MikroTikConfig.AddressList
|
||
}
|
||
return rule.Name // 默认使用规则名称
|
||
}
|
||
|
||
// getMikroTikComment 获取MikroTik备注
|
||
func (b *ConfigBuilder) getMikroTikComment(rule DomainRule) string {
|
||
if rule.MikroTikConfig.Comment != "" {
|
||
return rule.MikroTikConfig.Comment
|
||
}
|
||
return fmt.Sprintf("%s-AutoAdd", rule.Name) // 默认备注
|
||
}
|
||
|
||
// saveRuleConfig 保存规则配置到文件
|
||
func (b *ConfigBuilder) saveRuleConfig(filePath string, config *Config) error {
|
||
// 确保目录存在
|
||
dir := filepath.Dir(filePath)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return fmt.Errorf("创建目录失败: %w", err)
|
||
}
|
||
|
||
// 编码配置为YAML
|
||
var buf strings.Builder
|
||
encoder := yaml.NewEncoder(&buf)
|
||
encoder.SetIndent(2) // 设置缩进为2个空格
|
||
|
||
if err := encoder.Encode(config); err != nil {
|
||
return fmt.Errorf("编码YAML失败: %w", err)
|
||
}
|
||
|
||
// 写入文件
|
||
if err := os.WriteFile(filePath, []byte(buf.String()), 0644); err != nil {
|
||
return fmt.Errorf("写入文件失败: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// addRuleToIncludes 将规则添加到主配置的include中
|
||
func (b *ConfigBuilder) addRuleToIncludes(ruleFileName string) {
|
||
rulePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 检查是否已存在
|
||
for _, include := range b.baseConfig.Include {
|
||
if include == rulePath {
|
||
return // 已存在,无需添加
|
||
}
|
||
}
|
||
|
||
// 添加到include列表
|
||
b.baseConfig.Include = append(b.baseConfig.Include, rulePath)
|
||
}
|
||
|
||
// removeRuleFromIncludes 从主配置的include中移除规则
|
||
func (b *ConfigBuilder) removeRuleFromIncludes(ruleFileName string) {
|
||
rulePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 找到并移除
|
||
for i, include := range b.baseConfig.Include {
|
||
if include == rulePath {
|
||
b.baseConfig.Include = append(b.baseConfig.Include[:i], b.baseConfig.Include[i+1:]...)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// ListRules 列出所有规则
|
||
func (b *ConfigBuilder) ListRules() ([]DomainRule, error) {
|
||
var rules []DomainRule
|
||
|
||
// 确保规则目录存在
|
||
if _, err := os.Stat(b.rulesDir); os.IsNotExist(err) {
|
||
return rules, nil // 目录不存在,返回空列表
|
||
}
|
||
|
||
// 遍历规则文件
|
||
entries, err := os.ReadDir(b.rulesDir)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取规则目录失败: %w", err)
|
||
}
|
||
|
||
for _, entry := range entries {
|
||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
|
||
continue
|
||
}
|
||
|
||
ruleName := strings.TrimSuffix(entry.Name(), ".yaml")
|
||
rule, err := b.GetRule(ruleName)
|
||
if err != nil {
|
||
b.logger.Warn("加载规则失败",
|
||
zap.String("rule_name", ruleName),
|
||
zap.Error(err))
|
||
continue
|
||
}
|
||
|
||
rules = append(rules, rule)
|
||
}
|
||
|
||
return rules, nil
|
||
}
|
||
|
||
// GetRule 获取指定规则
|
||
func (b *ConfigBuilder) GetRule(ruleName string) (DomainRule, error) {
|
||
ruleFileName := ruleName + ".yaml"
|
||
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
|
||
|
||
// 检查规则文件是否存在
|
||
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
|
||
return DomainRule{}, fmt.Errorf("规则文件不存在: %s", ruleFilePath)
|
||
}
|
||
|
||
// 读取并解析规则文件
|
||
data, err := os.ReadFile(ruleFilePath)
|
||
if err != nil {
|
||
return DomainRule{}, fmt.Errorf("读取规则文件失败: %w", err)
|
||
}
|
||
|
||
var config Config
|
||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||
return DomainRule{}, fmt.Errorf("解析规则文件失败: %w", err)
|
||
}
|
||
|
||
// 从配置中提取规则信息
|
||
rule := b.parseRuleFromConfig(ruleName, &config)
|
||
return rule, nil
|
||
}
|
||
|
||
// parseRuleFromConfig 从配置中解析规则信息
|
||
func (b *ConfigBuilder) parseRuleFromConfig(ruleName string, config *Config) DomainRule {
|
||
rule := DomainRule{
|
||
Name: ruleName,
|
||
Enabled: true, // 默认启用
|
||
}
|
||
|
||
for _, plugin := range config.Plugins {
|
||
switch plugin.Type {
|
||
case "domain_set":
|
||
if args, ok := plugin.Args.(map[string]interface{}); ok {
|
||
if files, ok := args["files"].([]interface{}); ok && len(files) > 0 {
|
||
if file, ok := files[0].(string); ok {
|
||
rule.DomainFile = file
|
||
}
|
||
}
|
||
}
|
||
case "sequence":
|
||
if plugin.Tag == "rule_"+ruleName {
|
||
if args, ok := plugin.Args.(map[string]interface{}); ok {
|
||
if exec, ok := args["exec"].([]interface{}); ok && len(exec) > 0 {
|
||
if execMap, ok := exec[0].(map[string]interface{}); ok {
|
||
if exec, ok := execMap["exec"].(string); ok {
|
||
rule.DNSStrategy = strings.TrimPrefix(exec, "$")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
case "mikrotik_addresslist":
|
||
rule.EnableMikroTik = true
|
||
if args, ok := plugin.Args.(map[string]interface{}); ok {
|
||
rule.MikroTikConfig = MikroTikConfig{
|
||
Host: getStringValue(args, "host"),
|
||
Port: getIntValue(args, "port"),
|
||
Username: getStringValue(args, "username"),
|
||
Password: getStringValue(args, "password"),
|
||
AddressList: getStringValue(args, "address_list"),
|
||
Mask: getIntValue(args, "mask"),
|
||
MaxIPs: getIntValue(args, "max_ips"),
|
||
CacheTTL: getIntValue(args, "cache_ttl"),
|
||
TimeoutAddr: getIntValue(args, "timeout_addr"),
|
||
Comment: getStringValue(args, "comment"),
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return rule
|
||
}
|
||
|
||
// 辅助函数:获取字符串值
|
||
func getStringValue(args map[string]interface{}, key string) string {
|
||
if val, ok := args[key].(string); ok {
|
||
return val
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// 辅助函数:获取整数值
|
||
func getIntValue(args map[string]interface{}, key string) int {
|
||
if val, ok := args[key].(int); ok {
|
||
return val
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// Save 保存主配置
|
||
func (b *ConfigBuilder) Save() error {
|
||
// 主配置文件的路径
|
||
configPath := "config.yaml"
|
||
|
||
// 编码配置为YAML
|
||
var buf strings.Builder
|
||
encoder := yaml.NewEncoder(&buf)
|
||
encoder.SetIndent(2)
|
||
|
||
if err := encoder.Encode(b.baseConfig); err != nil {
|
||
return fmt.Errorf("编码主配置失败: %w", err)
|
||
}
|
||
|
||
// 写入主配置文件
|
||
if err := os.WriteFile(configPath, []byte(buf.String()), 0644); err != nil {
|
||
return fmt.Errorf("写入主配置失败: %w", err)
|
||
}
|
||
|
||
b.logger.Info("主配置保存成功", zap.String("file", configPath))
|
||
return nil
|
||
}
|