Spaces:
Paused
Paused
Upload 17 files
Browse files- app/main.py +156 -3
- app/sub_manager.py +43 -4
- entrypoint.sh +8 -9
app/main.py
CHANGED
|
@@ -225,6 +225,83 @@ def proxy_root():
|
|
| 225 |
"""处理根代理请求"""
|
| 226 |
return proxy_request("")
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
@app.route('/', methods=['GET'])
|
| 229 |
def index():
|
| 230 |
"""首页 - 提供简单说明"""
|
|
@@ -244,7 +321,77 @@ def index():
|
|
| 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'>
|
|
@@ -258,9 +405,15 @@ def index():
|
|
| 258 |
<li>健康检查: /health</li>
|
| 259 |
</ul>
|
| 260 |
<h2>操作</h2>
|
| 261 |
-
<
|
| 262 |
-
|
| 263 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
<h2>帮助</h2>
|
| 265 |
<p>更多信息请查看 <a href='https://github.com/yourusername/simple-clash-relay'>文档</a>。</p>
|
| 266 |
</div>
|
|
|
|
| 225 |
"""处理根代理请求"""
|
| 226 |
return proxy_request("")
|
| 227 |
|
| 228 |
+
@app.route("/debug/clean", methods=["POST"])
|
| 229 |
+
@authenticate
|
| 230 |
+
def debug_clean():
|
| 231 |
+
"""清理并重新初始化配置"""
|
| 232 |
+
global clash_manager, sub_manager, initialization_error
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
# 停止Clash(如果正在运行)
|
| 236 |
+
if clash_manager is not None:
|
| 237 |
+
clash_manager.stop_clash()
|
| 238 |
+
clash_manager = None
|
| 239 |
+
|
| 240 |
+
# 删除配置文件
|
| 241 |
+
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
|
| 242 |
+
raw_config_path = f"{config_path}.raw"
|
| 243 |
+
|
| 244 |
+
files_to_delete = [config_path, raw_config_path]
|
| 245 |
+
deleted_files = []
|
| 246 |
+
|
| 247 |
+
for file_path in files_to_delete:
|
| 248 |
+
if os.path.exists(file_path):
|
| 249 |
+
try:
|
| 250 |
+
os.remove(file_path)
|
| 251 |
+
deleted_files.append(file_path)
|
| 252 |
+
except Exception as e:
|
| 253 |
+
logger.error(f"删除文件 {file_path} 失败: {str(e)}")
|
| 254 |
+
|
| 255 |
+
# 重新初始化
|
| 256 |
+
sub_manager = SubscriptionManager(
|
| 257 |
+
sub_url=SUB_URL,
|
| 258 |
+
config_path=config_path
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
# 加载订阅并转换为Clash配置
|
| 262 |
+
sub_manager.load_and_convert_sub()
|
| 263 |
+
|
| 264 |
+
# 初始化Clash管理器
|
| 265 |
+
clash_manager = ClashManager(
|
| 266 |
+
config_path=config_path,
|
| 267 |
+
clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
|
| 268 |
+
api_port=CLASH_API_PORT,
|
| 269 |
+
proxy_port=CLASH_PROXY_PORT
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
# 启动Clash
|
| 273 |
+
clash_manager.start_clash()
|
| 274 |
+
initialization_error = None
|
| 275 |
+
|
| 276 |
+
return jsonify({
|
| 277 |
+
"success": True,
|
| 278 |
+
"message": f"配置已清理并重新初始化,删除的文件: {', '.join(deleted_files)}"
|
| 279 |
+
})
|
| 280 |
+
|
| 281 |
+
except Exception as e:
|
| 282 |
+
error_msg = f"清理配置失败: {str(e)}"
|
| 283 |
+
logger.error(error_msg)
|
| 284 |
+
initialization_error = error_msg
|
| 285 |
+
return jsonify({"success": False, "error": error_msg}), 500
|
| 286 |
+
|
| 287 |
+
@app.route("/debug/config", methods=["GET"])
|
| 288 |
+
@authenticate
|
| 289 |
+
def debug_show_config():
|
| 290 |
+
"""显示当前配置文件内容"""
|
| 291 |
+
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
|
| 292 |
+
|
| 293 |
+
if not os.path.exists(config_path):
|
| 294 |
+
return jsonify({"success": False, "error": "配置文件不存在"}), 404
|
| 295 |
+
|
| 296 |
+
try:
|
| 297 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 298 |
+
content = f.read()
|
| 299 |
+
|
| 300 |
+
return jsonify({"success": True, "content": content})
|
| 301 |
+
|
| 302 |
+
except Exception as e:
|
| 303 |
+
return jsonify({"success": False, "error": f"读取配置文件失败: {str(e)}"}), 500
|
| 304 |
+
|
| 305 |
@app.route('/', methods=['GET'])
|
| 306 |
def index():
|
| 307 |
"""首页 - 提供简单说明"""
|
|
|
|
| 321 |
.running {{ background-color: #dff0d8; color: #3c763d; }}
|
| 322 |
.error {{ background-color: #f2dede; color: #a94442; }}
|
| 323 |
.container {{ max-width: 800px; margin: 0 auto; }}
|
| 324 |
+
.button {{
|
| 325 |
+
display: inline-block;
|
| 326 |
+
padding: 8px 16px;
|
| 327 |
+
background-color: #337ab7;
|
| 328 |
+
color: white;
|
| 329 |
+
text-decoration: none;
|
| 330 |
+
border-radius: 4px;
|
| 331 |
+
margin-right: 10px;
|
| 332 |
+
}}
|
| 333 |
+
.button.danger {{ background-color: #d9534f; }}
|
| 334 |
+
.debug-section {{
|
| 335 |
+
margin-top: 20px;
|
| 336 |
+
padding: 15px;
|
| 337 |
+
border: 1px dashed #ccc;
|
| 338 |
+
background-color: #f9f9f9;
|
| 339 |
+
}}
|
| 340 |
</style>
|
| 341 |
+
<script>
|
| 342 |
+
function cleanConfig() {{
|
| 343 |
+
if (confirm('确定要清理配置并重启服务吗?')) {{
|
| 344 |
+
fetch('/debug/clean', {{
|
| 345 |
+
method: 'POST',
|
| 346 |
+
headers: {{ 'X-API-Key': prompt('请输入API密钥') }}
|
| 347 |
+
}})
|
| 348 |
+
.then(response => response.json())
|
| 349 |
+
.then(data => {{
|
| 350 |
+
alert(data.success ? data.message : '失败: ' + data.error);
|
| 351 |
+
location.reload();
|
| 352 |
+
}})
|
| 353 |
+
.catch(error => alert('请求失败: ' + error));
|
| 354 |
+
}}
|
| 355 |
+
}}
|
| 356 |
+
|
| 357 |
+
function viewConfig() {{
|
| 358 |
+
fetch('/debug/config', {{
|
| 359 |
+
headers: {{ 'X-API-Key': prompt('请输入API密钥') }}
|
| 360 |
+
}})
|
| 361 |
+
.then(response => response.json())
|
| 362 |
+
.then(data => {{
|
| 363 |
+
if (data.success) {{
|
| 364 |
+
const pre = document.createElement('pre');
|
| 365 |
+
pre.textContent = data.content;
|
| 366 |
+
pre.style.maxHeight = '500px';
|
| 367 |
+
pre.style.overflow = 'auto';
|
| 368 |
+
pre.style.backgroundColor = '#f5f5f5';
|
| 369 |
+
pre.style.padding = '10px';
|
| 370 |
+
pre.style.borderRadius = '4px';
|
| 371 |
+
|
| 372 |
+
const configDiv = document.getElementById('config-content');
|
| 373 |
+
configDiv.innerHTML = '';
|
| 374 |
+
configDiv.appendChild(pre);
|
| 375 |
+
}} else {{
|
| 376 |
+
alert('获取配置失败: ' + data.error);
|
| 377 |
+
}}
|
| 378 |
+
}})
|
| 379 |
+
.catch(error => alert('请求失败: ' + error));
|
| 380 |
+
}}
|
| 381 |
+
|
| 382 |
+
function refreshSubscription() {{
|
| 383 |
+
fetch('/api/refresh', {{
|
| 384 |
+
method: 'POST',
|
| 385 |
+
headers: {{ 'X-API-Key': prompt('请输入API密钥') }}
|
| 386 |
+
}})
|
| 387 |
+
.then(response => response.json())
|
| 388 |
+
.then(data => {{
|
| 389 |
+
alert(data.success ? data.message : '失败: ' + data.error);
|
| 390 |
+
location.reload();
|
| 391 |
+
}})
|
| 392 |
+
.catch(error => alert('请求失败: ' + error));
|
| 393 |
+
}}
|
| 394 |
+
</script>
|
| 395 |
</head>
|
| 396 |
<body>
|
| 397 |
<div class='container'>
|
|
|
|
| 405 |
<li>健康检查: /health</li>
|
| 406 |
</ul>
|
| 407 |
<h2>操作</h2>
|
| 408 |
+
<button class="button" onclick="refreshSubscription()">刷新订阅</button>
|
| 409 |
+
|
| 410 |
+
<div class="debug-section">
|
| 411 |
+
<h3>调试选项</h3>
|
| 412 |
+
<button class="button" onclick="viewConfig()">查看配置文件</button>
|
| 413 |
+
<button class="button danger" onclick="cleanConfig()">清理配置并重启</button>
|
| 414 |
+
<div id="config-content" style="margin-top: 15px;"></div>
|
| 415 |
+
</div>
|
| 416 |
+
|
| 417 |
<h2>帮助</h2>
|
| 418 |
<p>更多信息请查看 <a href='https://github.com/yourusername/simple-clash-relay'>文档</a>。</p>
|
| 419 |
</div>
|
app/sub_manager.py
CHANGED
|
@@ -128,6 +128,20 @@ class SubscriptionManager:
|
|
| 128 |
except Exception as e:
|
| 129 |
logger.error(f"创建数据目录失败: {str(e)}")
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
# 准备subconverter命令
|
| 132 |
cmd = [
|
| 133 |
self.subconverter_path,
|
|
@@ -177,6 +191,11 @@ class SubscriptionManager:
|
|
| 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("尝试直接使用订阅内容作为配置文件")
|
|
@@ -213,6 +232,11 @@ class SubscriptionManager:
|
|
| 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("使用订阅内容作为配置文件")
|
|
@@ -224,6 +248,20 @@ class SubscriptionManager:
|
|
| 224 |
logger.error(f"执行subconverter时出错: {str(e)}")
|
| 225 |
raise RuntimeError(f"配置转换失败: {str(e)}")
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
def _patch_config(self):
|
| 228 |
"""
|
| 229 |
修改配置文件以确保端口设置正确,并兼容Clash Meta
|
|
@@ -244,23 +282,24 @@ class SubscriptionManager:
|
|
| 244 |
# 这里需要检查配置是否为有效的YAML并进行适当修补
|
| 245 |
# 为简单起见,我们只检查和添加一些基本端口配置
|
| 246 |
|
| 247 |
-
if "port:
|
| 248 |
# 添加混合端口配置
|
| 249 |
config_content = "mixed-port: 7890\n" + config_content
|
| 250 |
has_patch = True
|
| 251 |
|
| 252 |
-
|
|
|
|
| 253 |
# 添加API控制器配置 (兼容Clash Meta)
|
| 254 |
config_content = "external-controller: 127.0.0.1:9090\n" + config_content
|
| 255 |
has_patch = True
|
| 256 |
|
| 257 |
# Clash Meta特定配置
|
| 258 |
-
if "find-process-mode:
|
| 259 |
config_content = "find-process-mode: strict\n" + config_content
|
| 260 |
has_patch = True
|
| 261 |
|
| 262 |
# 确保启用了API
|
| 263 |
-
if "secret:
|
| 264 |
config_content = "secret: ''\n" + config_content
|
| 265 |
has_patch = True
|
| 266 |
|
|
|
|
| 128 |
except Exception as e:
|
| 129 |
logger.error(f"创建数据目录失败: {str(e)}")
|
| 130 |
|
| 131 |
+
# 尝试直接读取订阅内容,确认它是否已经是Clash配置
|
| 132 |
+
try:
|
| 133 |
+
with open(input_file, "r", encoding="utf-8") as f:
|
| 134 |
+
content = f.read()
|
| 135 |
+
|
| 136 |
+
# 简单检查是否已经是Clash配置
|
| 137 |
+
if "proxies:" in content and ("port:" in content or "mixed-port:" in content):
|
| 138 |
+
logger.info("检测到输入文件已是Clash配置格式,直接使用")
|
| 139 |
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
| 140 |
+
f.write(content)
|
| 141 |
+
return
|
| 142 |
+
except Exception as e:
|
| 143 |
+
logger.warning(f"读取输入文件时出错: {str(e)},将尝试转换")
|
| 144 |
+
|
| 145 |
# 准备subconverter命令
|
| 146 |
cmd = [
|
| 147 |
self.subconverter_path,
|
|
|
|
| 191 |
try:
|
| 192 |
with open(input_file, "r", encoding="utf-8") as f:
|
| 193 |
content = f.read()
|
| 194 |
+
|
| 195 |
+
# 确保它是有效的配置,如果是普通订阅格式,添加基本的Clash头
|
| 196 |
+
if "proxies:" not in content:
|
| 197 |
+
content = self._add_clash_headers() + content
|
| 198 |
+
|
| 199 |
with open(self.config_path, "w", encoding="utf-8") as f:
|
| 200 |
f.write(content)
|
| 201 |
logger.warning("尝试直接使用订阅内容作为配置文件")
|
|
|
|
| 232 |
try:
|
| 233 |
with open(input_file, "r", encoding="utf-8") as f:
|
| 234 |
content = f.read()
|
| 235 |
+
|
| 236 |
+
# 确保它是有效的配置,如果是普通订阅格式,添加基本的Clash头
|
| 237 |
+
if "proxies:" not in content:
|
| 238 |
+
content = self._add_clash_headers() + content
|
| 239 |
+
|
| 240 |
with open(self.config_path, "w", encoding="utf-8") as f:
|
| 241 |
f.write(content)
|
| 242 |
logger.warning("使用订阅内容作为配置文件")
|
|
|
|
| 248 |
logger.error(f"执行subconverter时出错: {str(e)}")
|
| 249 |
raise RuntimeError(f"配置转换失败: {str(e)}")
|
| 250 |
|
| 251 |
+
def _add_clash_headers(self):
|
| 252 |
+
"""添加基本的Clash配置头"""
|
| 253 |
+
return """# 自动生成的Clash配置
|
| 254 |
+
port: 7890
|
| 255 |
+
socks-port: 7891
|
| 256 |
+
mixed-port: 7890
|
| 257 |
+
allow-lan: true
|
| 258 |
+
mode: Rule
|
| 259 |
+
log-level: info
|
| 260 |
+
external-controller: 127.0.0.1:9090
|
| 261 |
+
secret: ""
|
| 262 |
+
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
def _patch_config(self):
|
| 266 |
"""
|
| 267 |
修改配置文件以确保端口设置正确,并兼容Clash Meta
|
|
|
|
| 282 |
# 这里需要检查配置是否为有效的YAML并进行适当修补
|
| 283 |
# 为简单起见,我们只检查和添加一些基本端口配置
|
| 284 |
|
| 285 |
+
if "mixed-port:" not in config_content and "port:" not in config_content:
|
| 286 |
# 添加混合端口配置
|
| 287 |
config_content = "mixed-port: 7890\n" + config_content
|
| 288 |
has_patch = True
|
| 289 |
|
| 290 |
+
# 不要添加重复的external-controller配置
|
| 291 |
+
if "external-controller:" not in config_content:
|
| 292 |
# 添加API控制器配置 (兼容Clash Meta)
|
| 293 |
config_content = "external-controller: 127.0.0.1:9090\n" + config_content
|
| 294 |
has_patch = True
|
| 295 |
|
| 296 |
# Clash Meta特定配置
|
| 297 |
+
if "find-process-mode:" not in config_content:
|
| 298 |
config_content = "find-process-mode: strict\n" + config_content
|
| 299 |
has_patch = True
|
| 300 |
|
| 301 |
# 确保启用了API
|
| 302 |
+
if "secret:" not in config_content:
|
| 303 |
config_content = "secret: ''\n" + config_content
|
| 304 |
has_patch = True
|
| 305 |
|
entrypoint.sh
CHANGED
|
@@ -16,17 +16,16 @@ uname -a
|
|
| 16 |
echo "${YELLOW}Running as user:${NC}"
|
| 17 |
id
|
| 18 |
|
| 19 |
-
echo "${YELLOW}Checking
|
| 20 |
file /app/clash_core/clash.meta-linux-amd64
|
| 21 |
ls -la /app/clash_core/clash.meta-linux-amd64
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
echo "${YELLOW}
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
ldd /app/subconverter/subconverter || echo "ldd command failed"
|
| 30 |
|
| 31 |
# 确保数据目录存在
|
| 32 |
mkdir -p /app/data || echo "${YELLOW}Warning: Failed to create /app/data directory${NC}"
|
|
|
|
| 16 |
echo "${YELLOW}Running as user:${NC}"
|
| 17 |
id
|
| 18 |
|
| 19 |
+
echo "${YELLOW}Checking executables:${NC}"
|
| 20 |
file /app/clash_core/clash.meta-linux-amd64
|
| 21 |
ls -la /app/clash_core/clash.meta-linux-amd64
|
| 22 |
+
|
| 23 |
+
# 可选清理旧配置(如果指定CLEAN_CONFIG=true)
|
| 24 |
+
if [ "${CLEAN_CONFIG}" = "true" ]; then
|
| 25 |
+
echo "${YELLOW}Cleaning old config files...${NC}"
|
| 26 |
+
rm -f /app/data/config.yaml /app/data/config.yaml.raw
|
| 27 |
+
echo "${GREEN}Old config files cleaned${NC}"
|
| 28 |
+
fi
|
|
|
|
| 29 |
|
| 30 |
# 确保数据目录存在
|
| 31 |
mkdir -p /app/data || echo "${YELLOW}Warning: Failed to create /app/data directory${NC}"
|