clash-linux commited on
Commit
ff9409f
·
verified ·
1 Parent(s): fda2131

Upload 21 files

Browse files
Files changed (4) hide show
  1. .env +29 -0
  2. README.md +168 -68
  3. app/main.py +98 -4
  4. requirements.txt +3 -1
.env ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple Clash Relay 环境变量配置
2
+
3
+ # 必需:订阅链接
4
+ # 这是您的机场提供的订阅链接,用于获取节点列表
5
+ SUB_URL=https://xn--cp3a08l.com/api/v1/client/subscribe?token=your-token
6
+
7
+ # 必需:API密钥
8
+ # 用于保护API接口,请设置一个强密码
9
+ API_KEY=clash-proxy-key
10
+
11
+ # 可选:端口配置
12
+ # -------------
13
+ # Flask应用监听端口(默认7860,Hugging Face Spaces要求)
14
+ # 用于访问Web界面和API
15
+ FLASK_PORT=7860
16
+
17
+ # Clash代理监听端口(默认7890)
18
+ # 用于实际的代理服务
19
+ CLASH_PROXY_PORT=7890
20
+
21
+ # Clash API监听端口(默认9090)
22
+ # 用于Clash内部API通信,不需要对外暴露
23
+ CLASH_API_PORT=9090
24
+
25
+ # 可选:Worker进程数量(默认为CPU核心数+1)
26
+ # WORKER_COUNT=4
27
+
28
+ # 可选:日志级别(默认INFO)
29
+ LOG_LEVEL=INFO
README.md CHANGED
@@ -8,22 +8,33 @@ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
- # Simple Clash Relay
12
 
13
- 一个轻量级的自建Clash代理服务,可通过API控制节点切换。
14
 
15
  ## 功能特点
16
 
17
- - 🚀 **轻量级**:基于Python Flask和Clash Core,最小化依赖
18
- - 🔄 **机场订阅支持**:自动下载并转换您的机场订阅链接
19
- - 🔌 **多端口服务**:
 
 
 
 
 
 
 
 
20
  - **7860**: Web界面和API服务(Hugging Face默认端口)
21
- - **7890**: Clash代理服务
22
- - **9090**: Clash内部API服务
23
- - 🔒 **API认证**:通过API Key保护控制API
24
- - 🔄 **动态切换节点**:通过API随时切换使用的节点
25
- - 🐳 **容器化**:完整的Docker支持,便于部署
26
- - 🔥 **Hugging Face友好**:针对Hugging Face Spaces平台优化
 
 
 
27
 
28
  ## 系统要求
29
 
@@ -130,95 +141,184 @@ simple-clash-relay/
130
  - `SUB_URL`: 你的订阅链接
131
  - `API_KEY`: 你的API密钥
132
 
133
- ## API使用
134
 
135
- 所有API请求需要在请求头中包含`X-API-Key: 你的API密钥`。
136
 
137
- ### 获取节点列表
138
 
139
- ```
140
- GET /api/nodes
141
- ```
142
 
143
- 响应:
144
- ```json
145
- {
146
- "success": true,
147
- "nodes": ["节点A", "节点B", "节点C"]
148
- }
149
- ```
150
 
151
- ### 切换节点
152
 
153
- ```
154
- PUT /api/switch
155
- Content-Type: application/json
 
156
 
157
- {
158
- "node": "节点B"
 
159
  }
 
 
160
  ```
161
 
162
- 响应:
163
- ```json
164
- {
165
- "success": true,
166
- "message": "已切换到节点: 节点B"
167
- }
168
  ```
169
 
170
- ### 获取当前节点
 
 
171
 
172
  ```
173
- GET /api/current
 
174
  ```
175
 
176
- 响应:
177
- ```json
178
- {
179
- "success": true,
180
- "current_node": "节点B"
181
- }
182
  ```
183
 
184
- ### 刷新订阅
185
 
186
- ```
187
- POST /api/refresh
188
  ```
189
 
190
- 响应:
191
- ```json
192
- {
193
- "success": true,
194
- "message": "订阅已刷新,Clash已重启"
195
- }
196
  ```
197
 
198
- ## 在应用中使用代理
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
- 您可以通过以下方式使用代理服务:
 
 
 
201
 
202
- ### 标准模式
203
- - HTTP代理: `http://your-server-ip:7890`
204
- - SOCKS5代理: `socks5://your-server-ip:7890`
205
 
206
- ### Hugging Face Spaces模式 (代理路径转发)
207
- - HTTP代理: `http://your-space-name.hf.space/proxy`
208
- - SOCKS5代理: `socks5://your-space-name.hf.space/proxy`
209
 
210
- ### 在cursor-to-openai项目中配置
211
 
212
- 修改您的cursor-to-openai项目配置,设置代理地址:
213
- - 本地部署:`http://localhost:7890`
214
- - Hugging Face部署:`http://your-space-name.hf.space/proxy`
 
 
 
215
 
216
  ## 注意事项
217
 
218
- - 本项目仅用于个人学习和研究目的
219
- - 请遵守当地法律法规,不要用于非法用途
220
- - 确保设置强密码API Key以保护接口
221
  - Hugging Face Spaces有资源和流量限制,请合理使用
 
 
222
 
223
  ## 许可
224
 
 
8
  pinned: false
9
  ---
10
 
11
+ # Clash Proxy Service
12
 
13
+ 一个轻量级的自建Clash代理服务,适用于Hugging Face部署,可通过Web UI和API控制。提供HTTP和SOCKS5代理,支持订阅链接和手动配置文件上传。
14
 
15
  ## 功能特点
16
 
17
+ - 🚀 **轻量级架构**:基于Python Flask和Clash Meta,资源占用小
18
+ - 🔄 **多种配置方式**:
19
+ - 支持机场订阅链接自动下载转换
20
+ - 支持手动上传Clash配置文件
21
+ - 🌐 **完整的代理功能**:
22
+ - HTTP/HTTPS代理 (端口7890)
23
+ - SOCKS5代理 (端口7890)
24
+ - 🖥️ **内置控制面板**:
25
+ - 集成Yacd控制界面,可视化管理代理
26
+ - 基础的操作面板,简化常用操作
27
+ - 🔌 **暴露的端口**:
28
  - **7860**: Web界面和API服务(Hugging Face默认端口)
29
+ - **7890**: Clash代理服务(HTTP/SOCKS5)
30
+ - **9090**: Clash内部API服务(通常不需直接访问)
31
+ - 🔒 **安全性设计**:
32
+ - API密钥保护所有控制接口
33
+ - 可选的配置加密存储
34
+ - 🌍 **兼容性**:
35
+ - 适用于Hugging Face Spaces
36
+ - 支持Docker本地部署
37
+ - 兼容各类Clash客户端
38
 
39
  ## 系统要求
40
 
 
141
  - `SUB_URL`: 你的订阅链接
142
  - `API_KEY`: 你的API密钥
143
 
144
+ ## 使用教程:为其他项目提供代理
145
 
146
+ ### 基本代理地址
147
 
148
+ 服务部署成功后,您可以使用以下地址作为代理:
149
 
150
+ 对于部署在Hugging Face上的服务:
151
+ - **HTTP/HTTPS代理**:`https://clash-linux-clash.hf.space/proxy`
152
+ - **SOCKS5代理**:`socks5://clash-linux-clash.hf.space:7890`(注意:SOCKS5在Hugging Face上可能受限)
153
 
154
+ 对于本地部署的服务:
155
+ - **HTTP/HTTPS代理**:`http://localhost:7890`
156
+ - **SOCKS5代理**:`socks5://localhost:7890`
 
 
 
 
157
 
158
+ ### 在不同场景中使用代理
159
 
160
+ #### 1. Python程序
161
+
162
+ ```python
163
+ import requests
164
 
165
+ proxies = {
166
+ "http": "http://clash-linux-clash.hf.space/proxy",
167
+ "https": "http://clash-linux-clash.hf.space/proxy"
168
  }
169
+
170
+ response = requests.get("https://api.openai.com/v1/...", proxies=proxies)
171
  ```
172
 
173
+ #### 2. curl命令
174
+
175
+ ```bash
176
+ curl -x http://clash-linux-clash.hf.space/proxy https://api.openai.com/v1/...
 
 
177
  ```
178
 
179
+ #### 3. 在其他Hugging Face项目中使用
180
+
181
+ 在其他Hugging Face项目的配置中,添加环境变量:
182
 
183
  ```
184
+ HTTP_PROXY=http://clash-linux-clash.hf.space/proxy
185
+ HTTPS_PROXY=http://clash-linux-clash.hf.space/proxy
186
  ```
187
 
188
+ #### 4. 在NPM项目中使用
189
+
190
+ ```bash
191
+ # 设置npm代理
192
+ npm config set proxy http://clash-linux-clash.hf.space/proxy
193
+ npm config set https-proxy http://clash-linux-clash.hf.space/proxy
194
  ```
195
 
196
+ #### 5. Docker容器中使用
197
 
198
+ ```bash
199
+ docker run -e HTTP_PROXY=http://clash-linux-clash.hf.space/proxy -e HTTPS_PROXY=http://clash-linux-clash.hf.space/proxy your-image
200
  ```
201
 
202
+ #### 6. Git中使用
203
+
204
+ ```bash
205
+ git config --global http.proxy http://clash-linux-clash.hf.space/proxy
206
+ git config --global https.proxy http://clash-linux-clash.hf.space/proxy
 
207
  ```
208
 
209
+ ### 切换代理节点
210
+
211
+ 为获得最佳性能,您可能需要切换使用的代理节点:
212
+
213
+ 1. 访问 `https://clash-linux-clash.hf.space/ui/`
214
+ 2. 首次使用时设置外部控制器地址为 `/clashapi`,密钥为您的API密钥(默认为`changeme`)
215
+ 3. 在Proxies选项卡中,选择GLOBAL策略组,然后选择一个可用的节点
216
+
217
+ ## 详细使用指南
218
+
219
+ ### 1. 配置管理
220
+
221
+ #### 使用订阅链接
222
+
223
+ 服务默认使用环境变量`SUB_URL`中配置的订阅链接。如需刷新订阅:
224
+
225
+ 1. 访问Web UI `https://clash-linux-clash.hf.space/`
226
+ 2. 点击"刷新订阅并重启Clash"按钮
227
+ 3. 输入API密钥(默认为`changeme`)
228
+
229
+ #### 上传自定义配置
230
+
231
+ 如果您有现成的Clash配置文件:
232
+
233
+ 1. 访问Web UI `https://clash-linux-clash.hf.space/`
234
+ 2. 在"���传手动配置"区域选择您的配置文件(.yaml或.yml)
235
+ 3. 点击"上传并应用配置"按钮
236
+ 4. 输入API密钥
237
+
238
+ #### 恢复使用订阅
239
+
240
+ 如果您想从手动配置恢复到使用订阅:
241
+
242
+ 1. 在"调试与恢复"区域点击"清理配置并重启"
243
+ 2. 确认操作并输入API密钥
244
+
245
+ ### 2. 查看当前配置
246
+
247
+ 您可以检查当前使用的配置文件内容:
248
+
249
+ 1. 在Web UI中点击"查看当前配置文件"
250
+ 2. 输入API密钥
251
+ 3. 配置内容将显示在页面上
252
+
253
+ ### 3. 代理控制面板(Yacd)
254
+
255
+ 高级用户可以使用内置的Yacd面板管理代理:
256
+
257
+ 1. 访问 `https://clash-linux-clash.hf.space/ui/`
258
+ 2. 首次使用时需配置:
259
+ - 外部控制器地址:`/clashapi`
260
+ - 密钥:您的API密钥(默认为`changeme`)
261
+ 3. 在此面板中,您可以:
262
+ - 切换节点
263
+ - 查看连接日志
264
+ - 管理规则
265
+ - 监控网络流量
266
+
267
+ ### 4. API接口
268
+
269
+ 系统提供了以下API接口,所有请求都需要在Header中包含`X-API-Key`:
270
+
271
+ - `GET /api/nodes` - 获取所有可用节点
272
+ - `GET /api/current` - 获取当前使用的节点
273
+ - `PUT /api/switch` - 切换到指定节点
274
+ - `POST /api/refresh` - 刷新订阅并重启Clash
275
+ - `POST /api/upload_config` - 上传自定义配置文件
276
+ - `POST /debug/clean` - 清理配置并重新初始化
277
+ - `GET /debug/config` - 获取当前配置文件内容
278
+
279
+ ### 5. 排错指南
280
+
281
+ 如果遇到问题,请尝试以下步骤:
282
+
283
+ 1. **代理连接失败**:
284
+ - 确认代理地址正确
285
+ - 检查是否选择了可用的节点
286
+ - 尝试切换到其他节点
287
+
288
+ 2. **节点无法连接**:
289
+ - 刷新订阅获取最新节点
290
+ - 检查配置文件是否正确
291
+ - 尝试上传新的配置文件
292
+
293
+ 3. **界面加载问题**:
294
+ - 清除浏览器缓存
295
+ - 使用无痕模式访问
296
+ - 检查浏览器控制台是否有错误信息
297
 
298
+ 4. **配置问题**:
299
+ - 使用"清理配置并重启"功能重置系统
300
+ - 检查订阅链接是否有效
301
+ - 检查上传的配置文件格式是否正确
302
 
303
+ ## 自定义部署
 
 
304
 
305
+ ### Docker环境变量
 
 
306
 
307
+ 自行部署时可使用以下环境变量:
308
 
309
+ - `SUB_URL`:订阅链接URL
310
+ - `API_KEY`:API访问密钥(默认为"changeme")
311
+ - `FLASK_PORT`:Web界面端口(默认7860)
312
+ - `CLASH_PROXY_PORT`:Clash代理端口(默认7890)
313
+ - `CLASH_API_PORT`:Clash API端口(默认9090)
314
+ - `CLEAN_CONFIG`:启动时是否清理配置(true/false)
315
 
316
  ## 注意事项
317
 
318
+ - 本项目仅供学习研究使用,请遵守相关法律法规
 
 
319
  - Hugging Face Spaces有资源和流量限制,请合理使用
320
+ - 为保障安全,请修改默认API密钥
321
+ - 上传的配置文件会覆盖订阅内容,如需恢复请使用清理功能
322
 
323
  ## 许可
324
 
app/main.py CHANGED
@@ -7,7 +7,10 @@ Simple Clash Relay - Flask 应用入口
7
 
8
  import os
9
  import logging
 
 
10
  from flask import Flask, request, jsonify, Response, redirect, send_from_directory, flash
 
11
  from .clash_manager import ClashManager
12
  from .sub_manager import SubscriptionManager
13
  from .auth import authenticate
@@ -15,6 +18,9 @@ import requests
15
  from functools import wraps
16
  from werkzeug.utils import secure_filename
17
  import time
 
 
 
18
 
19
  # 配置日志
20
  logging.basicConfig(
@@ -33,9 +39,10 @@ CLASH_API_PORT = int(os.environ.get("CLASH_API_PORT", 9090))
33
  # 添加标记文件路径
34
  MANUAL_CONFIG_MARKER = os.path.join(os.path.dirname(__file__), "data", ".use_manual_config")
35
 
36
- # 初始化Flask应用
37
  app = Flask(__name__, static_folder='static')
38
  app.secret_key = os.environ.get("FLASK_SECRET_KEY", "supersecretkey") # 用于flash消息
 
39
 
40
  # 初始化管理器
41
  clash_manager = None
@@ -433,6 +440,92 @@ def upload_config():
433
  os.remove(marker_path)
434
  return jsonify({"success": False, "error": f"处理上传文件时出错: {str(e)}"}), 500
435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  # --- 基础路由 ---
437
 
438
  @app.route('/', methods=['GET'])
@@ -646,6 +739,7 @@ def index():
646
  if __name__ == "__main__":
647
  # 如果直接运行此文件,将初始化应用并启动Flask服务器
648
  initialize_once()
649
- logger.info(f"启动Flask服务器,监听端口: {FLASK_PORT}")
650
- app.run(host="0.0.0.0", port=FLASK_PORT)
651
- app.run(host="0.0.0.0", port=FLASK_PORT)
 
 
7
 
8
  import os
9
  import logging
10
+ import socket
11
+ import threading
12
  from flask import Flask, request, jsonify, Response, redirect, send_from_directory, flash
13
+ from flask_sockets import Sockets # 导入 Sockets
14
  from .clash_manager import ClashManager
15
  from .sub_manager import SubscriptionManager
16
  from .auth import authenticate
 
18
  from functools import wraps
19
  from werkzeug.utils import secure_filename
20
  import time
21
+ import gevent # 导入 gevent
22
+ from gevent import pywsgi
23
+ from geventwebsocket.handler import WebSocketHandler # 导入 WebSocketHandler
24
 
25
  # 配置日志
26
  logging.basicConfig(
 
39
  # 添加标记文件路径
40
  MANUAL_CONFIG_MARKER = os.path.join(os.path.dirname(__file__), "data", ".use_manual_config")
41
 
42
+ # 初始化Flask应用 和 Sockets
43
  app = Flask(__name__, static_folder='static')
44
  app.secret_key = os.environ.get("FLASK_SECRET_KEY", "supersecretkey") # 用于flash消息
45
+ sockets = Sockets(app) # 初始化 Sockets
46
 
47
  # 初始化管理器
48
  clash_manager = None
 
440
  os.remove(marker_path)
441
  return jsonify({"success": False, "error": f"处理上传文件时出错: {str(e)}"}), 500
442
 
443
+ # --- 新增:WebSocket代理隧道 ---
444
+
445
+ def forward_websocket_to_tcp(ws, tcp_socket):
446
+ """从WebSocket读取数据并写入TCP Socket"""
447
+ try:
448
+ while not ws.closed:
449
+ message = ws.receive()
450
+ if message:
451
+ # logger.debug(f"WS -> TCP: 转发 {len(message)} 字节")
452
+ tcp_socket.sendall(message)
453
+ else:
454
+ # WebSocket连接关闭或收到空消息
455
+ break
456
+ except Exception as e:
457
+ logger.warning(f"WS -> TCP 转发错误: {e}")
458
+ finally:
459
+ logger.info("WS -> TCP 转发协程结束")
460
+ if not tcp_socket._closed:
461
+ tcp_socket.close()
462
+ if not ws.closed:
463
+ ws.close()
464
+
465
+ def forward_tcp_to_websocket(tcp_socket, ws):
466
+ """从TCP Socket读取数据并写入WebSocket"""
467
+ try:
468
+ while not ws.closed:
469
+ data = tcp_socket.recv(4096) # 每次最多读取4KB
470
+ if data:
471
+ # logger.debug(f"TCP -> WS: 转发 {len(data)} 字节")
472
+ ws.send(data)
473
+ else:
474
+ # TCP连接关闭
475
+ break
476
+ except Exception as e:
477
+ # 忽略WebSocket可能已经关闭的错误
478
+ if "closed" not in str(e).lower():
479
+ logger.warning(f"TCP -> WS 转发错误: {e}")
480
+ finally:
481
+ logger.info("TCP -> WS 转发协程结束")
482
+ if not tcp_socket._closed:
483
+ tcp_socket.close()
484
+ if not ws.closed:
485
+ ws.close()
486
+
487
+ @sockets.route('/wsproxy')
488
+ def websocket_proxy_tunnel(ws):
489
+ """处理WebSocket连接,建立到Clash代理端口的TCP隧道"""
490
+ global clash_manager, initialization_error
491
+
492
+ logger.info(f"收到新的WebSocket连接请求: {request.remote_addr}")
493
+
494
+ # 1. 检查Clash是否运行
495
+ if clash_manager is None:
496
+ logger.error(f"Clash服务未运行,无法建立WebSocket隧道")
497
+ ws.close(reason="Clash service is not running")
498
+ return
499
+
500
+ # 2. 建立到内部Clash代理端口的TCP连接
501
+ target_host = "127.0.0.1"
502
+ target_port = CLASH_PROXY_PORT # 7890
503
+ tcp_socket = None
504
+ try:
505
+ tcp_socket = socket.create_connection((target_host, target_port), timeout=5)
506
+ logger.info(f"成功连接到内部Clash代理端口: {target_host}:{target_port}")
507
+ except Exception as e:
508
+ logger.error(f"连接到内部Clash代理端口失败: {e}")
509
+ ws.close(reason=f"Failed to connect to internal proxy: {e}")
510
+ return
511
+
512
+ # 3. 创建两个协程进行双向数据转发
513
+ logger.info("启动WebSocket和TCP之间的双向转发协程...")
514
+ ws_to_tcp_greenlet = gevent.spawn(forward_websocket_to_tcp, ws, tcp_socket)
515
+ tcp_to_ws_greenlet = gevent.spawn(forward_tcp_to_websocket, tcp_socket, ws)
516
+
517
+ # 4. 等待任一协程结束 (表示连接中断)
518
+ try:
519
+ gevent.joinall([ws_to_tcp_greenlet, tcp_to_ws_greenlet], raise_error=True)
520
+ except Exception as e:
521
+ logger.warning(f"转发协程出现错误: {e}")
522
+ finally:
523
+ logger.info("WebSocket隧道连接已关闭")
524
+ if tcp_socket and not tcp_socket._closed:
525
+ tcp_socket.close()
526
+ if ws and not ws.closed:
527
+ ws.close()
528
+
529
  # --- 基础路由 ---
530
 
531
  @app.route('/', methods=['GET'])
 
739
  if __name__ == "__main__":
740
  # 如果直接运行此文件,将初始化应用并启动Flask服务器
741
  initialize_once()
742
+ logger.info(f"启动 Flask/Gevent WebSocket 服务器,监听端口: {FLASK_PORT}")
743
+ # 使用 gevent 的 WSGI 服务器来支持 WebSocket
744
+ server = pywsgi.WSGIServer(('0.0.0.0', FLASK_PORT), app, handler_class=WebSocketHandler)
745
+ server.serve_forever()
requirements.txt CHANGED
@@ -4,4 +4,6 @@ requests==2.28.1
4
  Werkzeug==2.0.1
5
  PyYAML==6.0.1
6
  # pyyaml==6.0 # 已通过 apk 安装
7
- # 添加与 Flask==2.0.1 兼容的版本
 
 
 
4
  Werkzeug==2.0.1
5
  PyYAML==6.0.1
6
  # pyyaml==6.0 # 已通过 apk 安装
7
+ # 添加与 Flask==2.0.1 兼容的版本
8
+ flask-sockets
9
+ gevent