clash-linux commited on
Commit
b58324b
·
verified ·
1 Parent(s): 5b7de09

Upload 16 files

Browse files
app/clash_manager.py CHANGED
@@ -47,7 +47,26 @@ class ClashManager:
47
 
48
  # 确保配置文件存在
49
  if not os.path.exists(self.config_path):
50
- raise FileNotFoundError(f"Clash配置文件未找到: {self.config_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  # 设置Clash命令行参数 (兼容Clash Meta)
53
  cmd = [
@@ -203,4 +222,83 @@ class ClashManager:
203
 
204
  def __del__(self):
205
  """析构函数,确保进程在对象销毁时被终止"""
206
- self.stop_clash()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  # 确保配置文件存在
49
  if not os.path.exists(self.config_path):
50
+ # 检查目录是否存在
51
+ config_dir = os.path.dirname(self.config_path)
52
+ if not os.path.exists(config_dir):
53
+ try:
54
+ os.makedirs(config_dir, exist_ok=True)
55
+ logger.info(f"创建了配置目录: {config_dir}")
56
+ except Exception as e:
57
+ logger.error(f"无法创建配置目录: {str(e)}")
58
+
59
+ # 如果目录存在但文件不存在,尝试创建一个基础配置
60
+ try:
61
+ with open(self.config_path, "w", encoding="utf-8") as f:
62
+ f.write(self._get_base_config())
63
+ logger.info(f"创建了基础配置文件: {self.config_path}")
64
+ except Exception as e:
65
+ logger.error(f"无法创建配置文件: {str(e)}")
66
+ raise FileNotFoundError(f"Clash配置文件未找到且无法创建: {self.config_path}")
67
+
68
+ # 验证配置文件内容
69
+ self._validate_config_file()
70
 
71
  # 设置Clash命令行参数 (兼容Clash Meta)
72
  cmd = [
 
222
 
223
  def __del__(self):
224
  """析构函数,确保进程在对象销毁时被终止"""
225
+ self.stop_clash()
226
+
227
+ def _validate_config_file(self):
228
+ """验证配置文件内容,确保基本配置存在"""
229
+ try:
230
+ with open(self.config_path, "r", encoding="utf-8") as f:
231
+ content = f.read()
232
+
233
+ # 检查文件大小
234
+ if len(content) < 10:
235
+ logger.warning(f"配置文件内容过短: {len(content)} 字节")
236
+ # 尝试使用基础配置替换
237
+ with open(self.config_path, "w", encoding="utf-8") as f:
238
+ f.write(self._get_base_config())
239
+ logger.info("已使用基础配置替换")
240
+ return
241
+
242
+ # 检查基本配置是否存在 (非严格YAML解析,简单文本检查)
243
+ missing_configs = []
244
+ if "mixed-port:" not in content and "port:" not in content:
245
+ missing_configs.append("端口配置")
246
+
247
+ if "proxies:" not in content:
248
+ missing_configs.append("代理配置")
249
+
250
+ if missing_configs:
251
+ logger.warning(f"配置文件缺少: {', '.join(missing_configs)}")
252
+ # 如果缺少关键配置,尝试修复
253
+ has_patch = False
254
+
255
+ if "mixed-port:" not in content and "port:" not in content:
256
+ content = f"mixed-port: {self.proxy_port}\n" + content
257
+ has_patch = True
258
+
259
+ if "external-controller:" not in content:
260
+ content = f"external-controller: 127.0.0.1:{self.api_port}\n" + content
261
+ has_patch = True
262
+
263
+ if "proxies:" not in content:
264
+ content += "\nproxies:\n - name: DIRECT\n type: Direct\n"
265
+ has_patch = True
266
+
267
+ if has_patch:
268
+ with open(self.config_path, "w", encoding="utf-8") as f:
269
+ f.write(content)
270
+ logger.info("已修补配置文件")
271
+
272
+ except Exception as e:
273
+ logger.error(f"验证配置文件时出错: {str(e)}")
274
+ # 如果验证失败,尝试使用基础配置
275
+ try:
276
+ with open(self.config_path, "w", encoding="utf-8") as f:
277
+ f.write(self._get_base_config())
278
+ logger.info("已使用基础配置替换")
279
+ except Exception as write_err:
280
+ logger.error(f"无法写入基础配置: {str(write_err)}")
281
+
282
+ def _get_base_config(self):
283
+ """返回基础Clash配置"""
284
+ return f"""# 基础Clash配置
285
+ mixed-port: {self.proxy_port}
286
+ allow-lan: true
287
+ mode: Rule
288
+ log-level: info
289
+ external-controller: 127.0.0.1:{self.api_port}
290
+ secret: ""
291
+
292
+ proxies:
293
+ - name: DIRECT
294
+ type: Direct
295
+
296
+ proxy-groups:
297
+ - name: GLOBAL
298
+ type: select
299
+ proxies:
300
+ - DIRECT
301
+
302
+ rules:
303
+ - MATCH,DIRECT
304
+ """
app/main.py CHANGED
@@ -33,11 +33,12 @@ app = Flask(__name__)
33
  # 初始化管理器
34
  clash_manager = None
35
  sub_manager = None
 
36
 
37
  @app.before_first_request
38
  def initialize():
39
  """应用首次请求前的初始化"""
40
- global clash_manager, sub_manager
41
 
42
  logger.info("正在初始化应用...")
43
 
@@ -52,8 +53,10 @@ def initialize():
52
  sub_manager.load_and_convert_sub()
53
  logger.info("成功加载并转换订阅")
54
  except Exception as e:
55
- logger.error(f"加载订阅失败: {str(e)}")
56
- raise
 
 
57
 
58
  # 初始化Clash管理器
59
  clash_manager = ClashManager(
@@ -68,13 +71,22 @@ def initialize():
68
  clash_manager.start_clash()
69
  logger.info("成功启动Clash Core")
70
  except Exception as e:
71
- logger.error(f"启动Clash Core失败: {str(e)}")
72
- raise
 
73
 
74
  @app.route("/api/nodes", methods=["GET"])
75
  @authenticate
76
  def get_nodes():
77
  """获取可用节点列表"""
 
 
 
 
 
 
 
 
78
  try:
79
  nodes = clash_manager.get_nodes()
80
  return jsonify({"success": True, "nodes": nodes})
@@ -86,6 +98,14 @@ def get_nodes():
86
  @authenticate
87
  def switch_node():
88
  """切换到指定节点"""
 
 
 
 
 
 
 
 
89
  data = request.get_json()
90
  if not data or "node" not in data:
91
  return jsonify({"success": False, "error": "缺少'node'参数"}), 400
@@ -102,6 +122,14 @@ def switch_node():
102
  @authenticate
103
  def get_current_node():
104
  """获取当前使用的节点"""
 
 
 
 
 
 
 
 
105
  try:
106
  current_node = clash_manager.get_current_node()
107
  return jsonify({"success": True, "current_node": current_node})
@@ -113,13 +141,40 @@ def get_current_node():
113
  @authenticate
114
  def refresh_subscription():
115
  """刷新订阅并重新加载Clash配置"""
 
 
116
  try:
 
 
 
 
 
 
 
 
117
  sub_manager.load_and_convert_sub()
118
- clash_manager.restart_clash()
119
- return jsonify({"success": True, "message": "订阅已刷新,Clash已重启"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  except Exception as e:
121
- logger.error(f"刷新订阅失败: {str(e)}")
122
- return jsonify({"success": False, "error": str(e)}), 500
 
 
123
 
124
  @app.route("/health", methods=["GET"])
125
  def health_check():
@@ -130,6 +185,14 @@ def health_check():
130
  @app.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'])
131
  def proxy_request(path):
132
  """代理请求转发到Clash Core"""
 
 
 
 
 
 
 
 
133
  target_url = f"http://127.0.0.1:{CLASH_PROXY_PORT}/{path}"
134
  logger.debug(f"转发请求到: {target_url}")
135
 
@@ -165,15 +228,42 @@ def proxy_root():
165
  @app.route('/', methods=['GET'])
166
  def index():
167
  """首页 - 提供简单说明"""
168
- return """
 
 
 
 
 
169
  <html>
170
- <head><title>Simple Clash Relay</title></head>
 
 
 
 
 
 
 
 
 
 
171
  <body>
172
- <h1>Simple Clash Relay</h1>
173
- <p>状态: 运行中</p>
174
- <p>API端点: /api/*</p>
175
- <p>代理端点: /proxy</p>
176
- <p>更多信息请查看文档。</p>
 
 
 
 
 
 
 
 
 
 
 
 
177
  </body>
178
  </html>
179
  """
 
33
  # 初始化管理器
34
  clash_manager = None
35
  sub_manager = None
36
+ initialization_error = None
37
 
38
  @app.before_first_request
39
  def initialize():
40
  """应用首次请求前的初始化"""
41
+ global clash_manager, sub_manager, initialization_error
42
 
43
  logger.info("正在初始化应用...")
44
 
 
53
  sub_manager.load_and_convert_sub()
54
  logger.info("成功加载并转换订阅")
55
  except Exception as e:
56
+ err_msg = f"加载订阅失败: {str(e)}"
57
+ logger.error(err_msg)
58
+ initialization_error = err_msg
59
+ return # 继续初始化,但不启动Clash
60
 
61
  # 初始化Clash管理器
62
  clash_manager = ClashManager(
 
71
  clash_manager.start_clash()
72
  logger.info("成功启动Clash Core")
73
  except Exception as e:
74
+ err_msg = f"启动Clash Core失败: {str(e)}"
75
+ logger.error(err_msg)
76
+ initialization_error = err_msg
77
 
78
  @app.route("/api/nodes", methods=["GET"])
79
  @authenticate
80
  def get_nodes():
81
  """获取可用节点列表"""
82
+ global clash_manager, initialization_error
83
+
84
+ if clash_manager is None:
85
+ return jsonify({
86
+ "success": False,
87
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
88
+ }), 503
89
+
90
  try:
91
  nodes = clash_manager.get_nodes()
92
  return jsonify({"success": True, "nodes": nodes})
 
98
  @authenticate
99
  def switch_node():
100
  """切换到指定节点"""
101
+ global clash_manager, initialization_error
102
+
103
+ if clash_manager is None:
104
+ return jsonify({
105
+ "success": False,
106
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
107
+ }), 503
108
+
109
  data = request.get_json()
110
  if not data or "node" not in data:
111
  return jsonify({"success": False, "error": "缺少'node'参数"}), 400
 
122
  @authenticate
123
  def get_current_node():
124
  """获取当前使用的节点"""
125
+ global clash_manager, initialization_error
126
+
127
+ if clash_manager is None:
128
+ return jsonify({
129
+ "success": False,
130
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
131
+ }), 503
132
+
133
  try:
134
  current_node = clash_manager.get_current_node()
135
  return jsonify({"success": True, "current_node": current_node})
 
141
  @authenticate
142
  def refresh_subscription():
143
  """刷新订阅并重新加载Clash配置"""
144
+ global clash_manager, sub_manager, initialization_error
145
+
146
  try:
147
+ # 尝试重新加载订阅
148
+ if sub_manager is None:
149
+ # 如果订阅管理器未初始化,重新创建它
150
+ sub_manager = SubscriptionManager(
151
+ sub_url=SUB_URL,
152
+ config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
153
+ )
154
+
155
  sub_manager.load_and_convert_sub()
156
+
157
+ # 如果Clash未启动,尝试启动它
158
+ if clash_manager is None:
159
+ clash_manager = ClashManager(
160
+ config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml"),
161
+ clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
162
+ api_port=CLASH_API_PORT,
163
+ proxy_port=CLASH_PROXY_PORT
164
+ )
165
+ clash_manager.start_clash()
166
+ initialization_error = None
167
+ return jsonify({"success": True, "message": "订阅已刷新,Clash已启动"})
168
+ else:
169
+ # 如果Clash已经启动,重启它
170
+ clash_manager.restart_clash()
171
+ initialization_error = None
172
+ return jsonify({"success": True, "message": "订阅已刷新,Clash已重启"})
173
  except Exception as e:
174
+ error_msg = f"刷新订阅失败: {str(e)}"
175
+ logger.error(error_msg)
176
+ initialization_error = error_msg
177
+ return jsonify({"success": False, "error": error_msg}), 500
178
 
179
  @app.route("/health", methods=["GET"])
180
  def health_check():
 
185
  @app.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'])
186
  def proxy_request(path):
187
  """代理请求转发到Clash Core"""
188
+ global clash_manager, initialization_error
189
+
190
+ if clash_manager is None:
191
+ return jsonify({
192
+ "success": False,
193
+ "error": f"Clash代理未启动: {initialization_error or '未知错误'}"
194
+ }), 503
195
+
196
  target_url = f"http://127.0.0.1:{CLASH_PROXY_PORT}/{path}"
197
  logger.debug(f"转发请求到: {target_url}")
198
 
 
228
  @app.route('/', methods=['GET'])
229
  def index():
230
  """首页 - 提供简单说明"""
231
+ global initialization_error
232
+
233
+ status = "运行中" if initialization_error is None else "初始化失败"
234
+ error_msg = "" if initialization_error is None else f"<p style='color:red'>错误: {initialization_error}</p>"
235
+
236
+ return f"""
237
  <html>
238
+ <head>
239
+ <title>Simple Clash Relay</title>
240
+ <style>
241
+ body {{ font-family: Arial, sans-serif; padding: 20px; }}
242
+ h1 {{ color: #333; }}
243
+ .status {{ padding: 10px; border-radius: 5px; display: inline-block; }}
244
+ .running {{ background-color: #dff0d8; color: #3c763d; }}
245
+ .error {{ background-color: #f2dede; color: #a94442; }}
246
+ .container {{ max-width: 800px; margin: 0 auto; }}
247
+ </style>
248
+ </head>
249
  <body>
250
+ <div class='container'>
251
+ <h1>Simple Clash Relay</h1>
252
+ <p>状态: <span class='status {"running" if initialization_error is None else "error"}'>{status}</span></p>
253
+ {error_msg}
254
+ <h2>功能</h2>
255
+ <ul>
256
+ <li>API端点: /api/* (需要认证)</li>
257
+ <li>代理端点: /proxy</li>
258
+ <li>健康检查: /health</li>
259
+ </ul>
260
+ <h2>操作</h2>
261
+ <p><a href='/api/refresh'>刷新订阅</a> (需要认证)</p>
262
+ <p><a href='/api/nodes'>查看可用节点</a> (需要认证)</p>
263
+ <p><a href='/api/current'>查看当前节点</a> (需要认证)</p>
264
+ <h2>帮助</h2>
265
+ <p>更多信息请查看 <a href='https://github.com/yourusername/simple-clash-relay'>文档</a>。</p>
266
+ </div>
267
  </body>
268
  </html>
269
  """
app/sub_manager.py CHANGED
@@ -117,6 +117,16 @@ class SubscriptionManager:
117
  RuntimeError: 如果转换失败
118
  """
119
  logger.info(f"正在将订阅转换为Clash配置")
 
 
 
 
 
 
 
 
 
 
120
 
121
  # 准备subconverter命令
122
  cmd = [
@@ -128,13 +138,25 @@ class SubscriptionManager:
128
  "--include-remarks", ".*" # 包含所有节点
129
  ]
130
 
 
 
131
  # 如果subconverter不存在或执行出错,我们就尝试直接使用订阅内容
132
  if not os.path.exists(self.subconverter_path):
133
  logger.warning("subconverter不存在,尝试直接使用订阅内容")
134
- with open(input_file, "r", encoding="utf-8") as f:
135
- content = f.read()
136
- with open(self.config_path, "w", encoding="utf-8") as f:
137
- f.write(content)
 
 
 
 
 
 
 
 
 
 
138
  return
139
 
140
  try:
@@ -147,16 +169,56 @@ class SubscriptionManager:
147
  )
148
  stdout, stderr = process.communicate(timeout=30)
149
 
 
 
150
  if process.returncode != 0:
151
  logger.error(f"subconverter执行失败: {stderr}")
152
  # 错误处理:尝试直接使用订阅内容
153
- with open(input_file, "r", encoding="utf-8") as f:
154
- content = f.read()
155
- with open(self.config_path, "w", encoding="utf-8") as f:
156
- f.write(content)
157
- logger.warning("尝试直接使用订阅内容作为配置文件")
 
 
 
 
 
 
 
 
 
158
  else:
159
  logger.info("成功转换配置")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  except (subprocess.SubprocessError, OSError) as e:
162
  logger.error(f"执行subconverter时出错: {str(e)}")
 
117
  RuntimeError: 如果转换失败
118
  """
119
  logger.info(f"正在将订阅转换为Clash配置")
120
+ logger.info(f"输入文件: {input_file}, 配置路径: {self.config_path}")
121
+
122
+ # 确保数据目录存在
123
+ data_dir = os.path.dirname(self.config_path)
124
+ if not os.path.exists(data_dir):
125
+ logger.info(f"创建数据目录: {data_dir}")
126
+ try:
127
+ os.makedirs(data_dir, exist_ok=True)
128
+ except Exception as e:
129
+ logger.error(f"创建数据目录失败: {str(e)}")
130
 
131
  # 准备subconverter命令
132
  cmd = [
 
138
  "--include-remarks", ".*" # 包含所有节点
139
  ]
140
 
141
+ logger.info(f"执行命令: {' '.join(cmd)}")
142
+
143
  # 如果subconverter不存在或执行出错,我们就尝试直接使用订阅内容
144
  if not os.path.exists(self.subconverter_path):
145
  logger.warning("subconverter不存在,尝试直接使用订阅内容")
146
+ try:
147
+ with open(input_file, "r", encoding="utf-8") as f:
148
+ content = f.read()
149
+ with open(self.config_path, "w", encoding="utf-8") as f:
150
+ f.write(content)
151
+ logger.info(f"已将订阅内容直接写入到: {self.config_path}")
152
+ # 验证文件是否成功写入
153
+ if os.path.exists(self.config_path):
154
+ logger.info(f"文件已成功写入,大小: {os.path.getsize(self.config_path)} 字节")
155
+ else:
156
+ logger.error(f"文件写入失败,{self.config_path} 不存在")
157
+ except Exception as e:
158
+ logger.error(f"直接使用订阅内容时出错: {str(e)}")
159
+ raise RuntimeError(f"写入配置文件失败: {str(e)}")
160
  return
161
 
162
  try:
 
169
  )
170
  stdout, stderr = process.communicate(timeout=30)
171
 
172
+ logger.info(f"subconverter输出: {stdout[:200]}...") # 限制日志长度
173
+
174
  if process.returncode != 0:
175
  logger.error(f"subconverter执行失败: {stderr}")
176
  # 错误处理:尝试直接使用订阅内容
177
+ try:
178
+ with open(input_file, "r", encoding="utf-8") as f:
179
+ content = f.read()
180
+ with open(self.config_path, "w", encoding="utf-8") as f:
181
+ f.write(content)
182
+ logger.warning("尝试直接使用订阅内容作为配置文件")
183
+ # 验证文件是否成功写入
184
+ if os.path.exists(self.config_path):
185
+ logger.info(f"文件已成功写入,大小: {os.path.getsize(self.config_path)} 字节")
186
+ else:
187
+ logger.error(f"文件写入失败,{self.config_path} 不存在")
188
+ except Exception as e:
189
+ logger.error(f"使用订阅内容作为配置文件时出错: {str(e)}")
190
+ raise RuntimeError(f"写入配置文件失败: {str(e)}")
191
  else:
192
  logger.info("成功转换配置")
193
+ # 验证输出文件是否存在
194
+ if os.path.exists(self.config_path):
195
+ logger.info(f"配置文件已生成,路径: {self.config_path},大小: {os.path.getsize(self.config_path)} 字节")
196
+ else:
197
+ # 如果文件不存在但subconverter返回成功,尝试查找配置文件
198
+ logger.warning(f"subconverter声称成功但配置文件不存在: {self.config_path}")
199
+ # 查找当前目录下可能生成的配置文件
200
+ possible_files = [f for f in os.listdir('.') if f.endswith('.yaml') or f.endswith('.yml')]
201
+ if possible_files:
202
+ logger.info(f"找到可能的配置文件: {possible_files}")
203
+ # 尝试复制找到的第一个文件
204
+ try:
205
+ import shutil
206
+ shutil.copy(possible_files[0], self.config_path)
207
+ logger.info(f"已复制 {possible_files[0]} 到 {self.config_path}")
208
+ except Exception as e:
209
+ logger.error(f"复制文件失败: {str(e)}")
210
+ else:
211
+ logger.error("未找到任何可能的配置文件")
212
+ # 尝试使用原始订阅内容作为配置
213
+ try:
214
+ with open(input_file, "r", encoding="utf-8") as f:
215
+ content = f.read()
216
+ with open(self.config_path, "w", encoding="utf-8") as f:
217
+ f.write(content)
218
+ logger.warning("使用订阅内容作为配置文件")
219
+ except Exception as e:
220
+ logger.error(f"使用订阅内容时出错: {str(e)}")
221
+ raise RuntimeError(f"写入配置文件失败: {str(e)}")
222
 
223
  except (subprocess.SubprocessError, OSError) as e:
224
  logger.error(f"执行subconverter时出错: {str(e)}")
subconverter/base/clash.yaml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ port: 7890
2
+ socks-port: 7891
3
+ mixed-port: 7890
4
+ allow-lan: true
5
+ mode: Rule
6
+ log-level: info
7
+ external-controller: 127.0.0.1:9090
8
+ secret: ""
9
+ dns:
10
+ enable: true
11
+ listen: 0.0.0.0:53
12
+ default-nameserver:
13
+ - 223.5.5.5
14
+ - 119.29.29.29
15
+ nameserver:
16
+ - 223.5.5.5
17
+ - 119.29.29.29
18
+ fallback: []
19
+ fake-ip-range: 198.18.0.1/16
20
+ use-hosts: true
21
+ proxies:
22
+ # 此处将被节点内容替换
23
+ proxy-groups:
24
+ - name: GLOBAL
25
+ type: select
26
+ proxies:
27
+ - DIRECT
28
+ # 此处将被策略组替换
29
+ rules:
30
+ # 此处将被规则替换
subconverter/templates/clash/config.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if request.target == "clash" or request.target == "clashr" %}
2
+ port: {{ default(local.clash.http_port, "7890") }}
3
+ socks-port: {{ default(local.clash.socks_port, "7891") }}
4
+ mixed-port: {{ default(local.clash.mixed_port, "7890") }}
5
+ allow-lan: {{ default(local.clash.allow_lan, "true") }}
6
+ mode: Rule
7
+ log-level: {{ default(local.clash.log_level, "info") }}
8
+ external-controller: {{ default(local.clash.api_port, "0.0.0.0:9090") }}
9
+ secret: {{ default(local.clash.api_secret, "") }}
10
+
11
+ {% if local.clash.new_field_name == "true" %}
12
+ dns:
13
+ enable: {{ default(local.clash.dns, "true") }}
14
+ listen: 0.0.0.0:53
15
+ nameserver:
16
+ - 223.5.5.5
17
+ - 119.29.29.29
18
+ fallback:
19
+ - 8.8.8.8
20
+ - 8.8.4.4
21
+ {% endif %}
22
+
23
+ proxies: ~
24
+
25
+ proxy-groups: ~
26
+
27
+ rules: ~
28
+ {% endif %}
subconverter/templates/clash/surge.yaml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if request.target == "surge" %}
2
+ [General]
3
+ loglevel = notify
4
+ bypass-system = true
5
+ skip-proxy = 127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,100.64.0.0/10,localhost,*.local,e.crashlytics.com,captive.apple.com,::ffff:0:0:0:0/1,::ffff:128:0:0:0/1
6
+ bypass-tun = 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12
7
+ dns-server = 119.29.29.29,223.5.5.5
8
+
9
+ [Proxy]
10
+ {{ getClashNodes(nodes) }}
11
+
12
+ [Proxy Group]
13
+ {{ getSurgeProxyGroups(groups) }}
14
+
15
+ [Rule]
16
+ {{ getSurgeRules(rules) }}
17
+ {% endif %}