clash-linux commited on
Commit
03d4200
·
verified ·
1 Parent(s): b58324b

Upload 17 files

Browse files
Files changed (5) hide show
  1. Dockerfile +13 -1
  2. app.py +38 -13
  3. app/clash_manager.py +91 -18
  4. app/debug_tools.py +177 -0
  5. entrypoint.sh +18 -0
Dockerfile CHANGED
@@ -16,7 +16,11 @@ RUN apk add --no-cache \
16
  musl-dev \
17
  libffi-dev \
18
  yaml-dev \
19
- py3-yaml
 
 
 
 
20
  # 不再需要 build-base,因为不再编译 PyYAML
21
 
22
  # 设置时区为亚洲/上海
@@ -35,6 +39,10 @@ RUN echo "Downloading Clash Meta..." && \
35
  gunzip -c /tmp/clash-meta.gz > ./clash_core/clash.meta-linux-amd64 && \
36
  echo "Setting Clash Meta permissions..." && \
37
  chmod +x ./clash_core/clash.meta-linux-amd64 && \
 
 
 
 
38
  echo "Verifying Clash Meta exists..." && \
39
  test -f ./clash_core/clash.meta-linux-amd64 && \
40
  echo "Cleaning up Clash Meta download..." && \
@@ -47,6 +55,10 @@ RUN echo "Downloading subconverter..." && \
47
  tar -xzf /tmp/subconverter.tar.gz -C ./subconverter --strip-components=1 && \
48
  echo "Setting subconverter permissions..." && \
49
  chmod +x ./subconverter/subconverter && \
 
 
 
 
50
  echo "Verifying subconverter exists..." && \
51
  test -f ./subconverter/subconverter && \
52
  echo "Cleaning up subconverter download..." && \
 
16
  musl-dev \
17
  libffi-dev \
18
  yaml-dev \
19
+ py3-yaml \
20
+ # 添加调试工具
21
+ file \
22
+ ldd \
23
+ strace
24
  # 不再需要 build-base,因为不再编译 PyYAML
25
 
26
  # 设置时区为亚洲/上海
 
39
  gunzip -c /tmp/clash-meta.gz > ./clash_core/clash.meta-linux-amd64 && \
40
  echo "Setting Clash Meta permissions..." && \
41
  chmod +x ./clash_core/clash.meta-linux-amd64 && \
42
+ # 确保Linux可执行属性已设置
43
+ ls -la ./clash_core/clash.meta-linux-amd64 && \
44
+ # 显示文件类型
45
+ file ./clash_core/clash.meta-linux-amd64 && \
46
  echo "Verifying Clash Meta exists..." && \
47
  test -f ./clash_core/clash.meta-linux-amd64 && \
48
  echo "Cleaning up Clash Meta download..." && \
 
55
  tar -xzf /tmp/subconverter.tar.gz -C ./subconverter --strip-components=1 && \
56
  echo "Setting subconverter permissions..." && \
57
  chmod +x ./subconverter/subconverter && \
58
+ # 确保Linux可执行属性已设置
59
+ ls -la ./subconverter/subconverter && \
60
+ # 显示文件类型
61
+ file ./subconverter/subconverter && \
62
  echo "Verifying subconverter exists..." && \
63
  test -f ./subconverter/subconverter && \
64
  echo "Cleaning up subconverter download..." && \
app.py CHANGED
@@ -18,20 +18,45 @@ def setup_environment():
18
  except Exception as e:
19
  print(f"⚠️ 创建数据目录失败: {str(e)}")
20
 
21
- # 尝试设置执行权限,但允许失败
22
- try:
23
- if os.path.exists("subconverter/subconverter"):
24
- os.chmod("subconverter/subconverter", 0o755)
25
- print(" subconverter权限已设置")
26
- except Exception as e:
27
- print(f"⚠️ 设置subconverter权限失败: {str(e)}")
28
 
29
- try:
30
- if os.path.exists("clash_core/clash.meta-linux-amd64"):
31
- os.chmod("clash_core/clash.meta-linux-amd64", 0o755)
32
- print("✅ Clash Core权限已设置")
33
- except Exception as e:
34
- print(f"⚠️ 设置Clash Core权限失败: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  # 设置环境变量
37
  os.environ["FLASK_PORT"] = "7860" # HF Spaces 要求的端口
 
18
  except Exception as e:
19
  print(f"⚠️ 创建数据目录失败: {str(e)}")
20
 
21
+ # 打印环境诊断信息
22
+ print("🔍 系统信息:")
23
+ print(f" - Python: {sys.version}")
24
+ print(f" - 工作目录: {os.getcwd()}")
25
+ print(f" - 用户: {os.getuid() if hasattr(os, 'getuid') else 'N/A'}")
 
 
26
 
27
+ # 检查二进制文件
28
+ for binary in ["subconverter/subconverter", "clash_core/clash.meta-linux-amd64"]:
29
+ if os.path.exists(binary):
30
+ try:
31
+ # 文件信息
32
+ print(f" 发现二进制文件: {binary}")
33
+ print(f" - 大小: {os.path.getsize(binary)} 字节")
34
+ print(f" - 权限: {oct(os.stat(binary).st_mode)[-3:]}")
35
+ print(f" - 可执行: {os.access(binary, os.X_OK)}")
36
+
37
+ # 尝试获取文件类型
38
+ try:
39
+ file_type = subprocess.check_output(["file", binary], universal_newlines=True)
40
+ print(f" - 类型: {file_type.strip()}")
41
+ except Exception as e:
42
+ print(f" - 获取文件类型失败: {str(e)}")
43
+
44
+ # 设置执行权限
45
+ os.chmod(binary, 0o755)
46
+ print(f"✅ {binary}权限已设置")
47
+ except Exception as e:
48
+ print(f"⚠️ 检查{binary}时出错: {str(e)}")
49
+ else:
50
+ print(f"❌ 找不到二进制文件: {binary}")
51
+
52
+ # 尝试执行 Clash 来获取版本信息
53
+ clash_path = "clash_core/clash.meta-linux-amd64"
54
+ if os.path.exists(clash_path):
55
+ try:
56
+ version_output = subprocess.check_output([clash_path, "-v"], stderr=subprocess.STDOUT, universal_newlines=True, timeout=2)
57
+ print(f"✅ Clash版本信息: {version_output.strip()}")
58
+ except Exception as e:
59
+ print(f"⚠️ 获取Clash版本信息失败: {str(e)}")
60
 
61
  # 设置环境变量
62
  os.environ["FLASK_PORT"] = "7860" # HF Spaces 要求的端口
app/clash_manager.py CHANGED
@@ -12,6 +12,7 @@ import logging
12
  import subprocess
13
  import requests
14
  import json
 
15
 
16
  logger = logging.getLogger(__name__)
17
 
@@ -68,6 +69,20 @@ class ClashManager:
68
  # 验证配置文件内容
69
  self._validate_config_file()
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # 设置Clash命令行参数 (兼容Clash Meta)
72
  cmd = [
73
  self.clash_path,
@@ -91,28 +106,86 @@ class ClashManager:
91
 
92
  # 启动Clash进程
93
  logger.info(f"正在启动Clash Core: {' '.join(cmd)}")
94
- self.clash_process = subprocess.Popen(
95
- cmd,
96
- stdout=subprocess.PIPE,
97
- stderr=subprocess.PIPE,
98
- universal_newlines=True
99
- )
100
 
101
- # 等待Clash启动
102
- time.sleep(2)
 
103
 
104
- # 检查进程是否成功启动
105
- if self.clash_process.poll() is not None:
106
- stderr = self.clash_process.stderr.read()
107
- raise RuntimeError(f"Clash启动失败: {stderr}")
108
-
109
- # 验证API是否可访问
110
  try:
111
- self._call_api("GET", "/version")
112
- logger.info("Clash API已就绪")
 
113
  except Exception as e:
114
- self.stop_clash()
115
- raise RuntimeError(f"无法连接到Clash API: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  def stop_clash(self):
118
  """停止Clash Core进程"""
 
12
  import subprocess
13
  import requests
14
  import json
15
+ from .debug_tools import run_debug_diagnostics
16
 
17
  logger = logging.getLogger(__name__)
18
 
 
69
  # 验证配置文件内容
70
  self._validate_config_file()
71
 
72
+ # 运行调试诊断
73
+ logger.info("运行环境诊断...")
74
+ try:
75
+ diagnostics = run_debug_diagnostics(self.clash_path, self.config_path)
76
+ if not diagnostics.get("clash_binary", {}).get("is_executable", False):
77
+ logger.warning("Clash Core不是可执行文件,尝试设置执行权限")
78
+ try:
79
+ os.chmod(self.clash_path, 0o755)
80
+ logger.info("已设置执行权限")
81
+ except Exception as e:
82
+ logger.error(f"设置执行权限失败: {str(e)}")
83
+ except Exception as e:
84
+ logger.error(f"运行诊断时出错: {str(e)}")
85
+
86
  # 设置Clash命令行参数 (兼容Clash Meta)
87
  cmd = [
88
  self.clash_path,
 
106
 
107
  # 启动Clash进程
108
  logger.info(f"正在启动Clash Core: {' '.join(cmd)}")
 
 
 
 
 
 
109
 
110
+ # 首先尝试检查Clash可执行文件是否存在并且可执行
111
+ if not os.path.exists(self.clash_path):
112
+ raise FileNotFoundError(f"Clash可执行文件未找到: {self.clash_path}")
113
 
114
+ # 检查Clash文件是否有执行权限
 
 
 
 
 
115
  try:
116
+ # 尝试设置执行权限
117
+ os.chmod(self.clash_path, 0o755)
118
+ logger.info(f"已设置Clash Core的执行权限")
119
  except Exception as e:
120
+ logger.warning(f"无法设置执行权限: {str(e)}, 尝试继续执行...")
121
+
122
+ # 启动进程并捕获输出
123
+ try:
124
+ self.clash_process = subprocess.Popen(
125
+ cmd,
126
+ stdout=subprocess.PIPE,
127
+ stderr=subprocess.PIPE,
128
+ universal_newlines=True
129
+ )
130
+
131
+ # 等待Clash启动
132
+ logger.info("等待Clash Core启动...")
133
+ time.sleep(2)
134
+
135
+ # 检查进程是否成功启动
136
+ if self.clash_process.poll() is not None:
137
+ # 如果进程已经退出,获取错误输出
138
+ stdout, stderr = self.clash_process.communicate()
139
+ logger.error(f"Clash启动失败,退出代码: {self.clash_process.returncode}")
140
+ logger.error(f"标准输出: {stdout}")
141
+ logger.error(f"错误输出: {stderr}")
142
+
143
+ # 如果没有错误输出,尝试读取配置文件周围的日志
144
+ if not stderr.strip():
145
+ try:
146
+ # 查看配置文件内容
147
+ with open(self.config_path, 'r', encoding='utf-8') as f:
148
+ config_content = f.read()
149
+ logger.error(f"配置文件内容预览 (前100字符): {config_content[:100]}")
150
+ except Exception as file_err:
151
+ logger.error(f"读取配置文件失败: {str(file_err)}")
152
+
153
+ # 创建更有意义的错误消息
154
+ error_message = stderr or stdout or "无错误信息,可能是程序无法执行或立即崩溃"
155
+ raise RuntimeError(f"Clash启动失败: {error_message}")
156
+
157
+ # 进程仍在运行,验证API是否可访问
158
+ logger.info("进程仍在运行,验证API...")
159
+
160
+ try:
161
+ # 尝试多次连接API,有时需要一些时间
162
+ max_retries = 3
163
+ for i in range(max_retries):
164
+ try:
165
+ self._call_api("GET", "/version")
166
+ logger.info("Clash API已就绪")
167
+ return # 成功,退出函数
168
+ except Exception as api_err:
169
+ if i < max_retries - 1:
170
+ logger.warning(f"API连接尝试 {i+1}/{max_retries} 失败: {str(api_err)},重试...")
171
+ time.sleep(1)
172
+ else:
173
+ # 最后一次尝试失败,捕获并记���错误
174
+ stdout, stderr = self.clash_process.communicate(timeout=1)
175
+ logger.error(f"API连接失败,标准输出: {stdout}")
176
+ logger.error(f"API连接失败,错误输出: {stderr}")
177
+ # 杀掉进程并抛出异常
178
+ self.stop_clash()
179
+ raise RuntimeError(f"无法连接到Clash API: {str(api_err)},进程输出: {stderr or stdout}")
180
+ except Exception as e:
181
+ # 捕获其他异常
182
+ self.stop_clash()
183
+ raise RuntimeError(f"验证API时出错: {str(e)}")
184
+
185
+ except (subprocess.SubprocessError, OSError) as e:
186
+ # 进程启动失败
187
+ logger.error(f"启动Clash进程时出错: {str(e)}")
188
+ raise RuntimeError(f"无法启动Clash进程: {str(e)}")
189
 
190
  def stop_clash(self):
191
  """停止Clash Core进程"""
app/debug_tools.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 调试工具 - 用于分析 Clash Core 的运行环境
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import subprocess
11
+ import logging
12
+ import platform
13
+ import json
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ def check_binary(binary_path):
18
+ """检查二进制文件,并返回详细信息"""
19
+ results = {
20
+ "path": binary_path,
21
+ "exists": os.path.exists(binary_path),
22
+ "size": 0,
23
+ "permissions": "",
24
+ "file_type": "",
25
+ "ldd_output": "",
26
+ "system_info": get_system_info()
27
+ }
28
+
29
+ if results["exists"]:
30
+ try:
31
+ # 获取文件大小
32
+ results["size"] = os.path.getsize(binary_path)
33
+
34
+ # 获取文件权限
35
+ results["permissions"] = oct(os.stat(binary_path).st_mode)[-3:]
36
+
37
+ # 检查是否可执行
38
+ results["is_executable"] = os.access(binary_path, os.X_OK)
39
+
40
+ # 获取文件类型 (file 命令)
41
+ try:
42
+ file_output = subprocess.check_output(["file", binary_path], universal_newlines=True)
43
+ results["file_type"] = file_output.strip()
44
+ except Exception as e:
45
+ results["file_type_error"] = str(e)
46
+
47
+ # 获取库依赖 (ldd 命令)
48
+ try:
49
+ ldd_output = subprocess.check_output(["ldd", binary_path], universal_newlines=True)
50
+ results["ldd_output"] = ldd_output.strip()
51
+ except Exception as e:
52
+ results["ldd_error"] = str(e)
53
+
54
+ # 尝试直接执行获取版本或帮助
55
+ try:
56
+ version_output = subprocess.check_output([binary_path, "-v"], stderr=subprocess.STDOUT, universal_newlines=True, timeout=2)
57
+ results["version_output"] = version_output.strip()
58
+ except Exception as e:
59
+ results["version_error"] = str(e)
60
+
61
+ # 如果 -v 失败,尝试 --version
62
+ try:
63
+ version_output = subprocess.check_output([binary_path, "--version"], stderr=subprocess.STDOUT, universal_newlines=True, timeout=2)
64
+ results["version_output"] = version_output.strip()
65
+ except Exception as e2:
66
+ results["version_error_alt"] = str(e2)
67
+
68
+ except Exception as e:
69
+ results["error"] = str(e)
70
+
71
+ return results
72
+
73
+ def get_system_info():
74
+ """获取系统信息"""
75
+ info = {
76
+ "platform": platform.platform(),
77
+ "system": platform.system(),
78
+ "release": platform.release(),
79
+ "version": platform.version(),
80
+ "architecture": platform.machine(),
81
+ "python_version": sys.version,
82
+ "cwd": os.getcwd(),
83
+ "env": {key: value for key, value in os.environ.items()}
84
+ }
85
+ return info
86
+
87
+ def check_clash_environment(clash_path, config_path):
88
+ """检查 Clash 运行环境"""
89
+ results = {
90
+ "clash_binary": check_binary(clash_path),
91
+ "config_file": {
92
+ "path": config_path,
93
+ "exists": os.path.exists(config_path),
94
+ "size": 0,
95
+ "content_preview": ""
96
+ },
97
+ "directory_info": {}
98
+ }
99
+
100
+ # 检查配置文件
101
+ if results["config_file"]["exists"]:
102
+ try:
103
+ results["config_file"]["size"] = os.path.getsize(config_path)
104
+ with open(config_path, 'r', encoding='utf-8') as f:
105
+ content = f.read(1000) # 读取前1000个字符
106
+ results["config_file"]["content_preview"] = content
107
+ except Exception as e:
108
+ results["config_file"]["error"] = str(e)
109
+
110
+ # 检查目录结构
111
+ dirs_to_check = [
112
+ os.path.dirname(clash_path),
113
+ os.path.dirname(config_path),
114
+ ".",
115
+ "./data",
116
+ "./clash_core",
117
+ "./subconverter"
118
+ ]
119
+
120
+ for dir_path in dirs_to_check:
121
+ try:
122
+ if os.path.exists(dir_path):
123
+ files = os.listdir(dir_path)
124
+ results["directory_info"][dir_path] = {
125
+ "exists": True,
126
+ "files": files,
127
+ "permissions": oct(os.stat(dir_path).st_mode)[-3:]
128
+ }
129
+ else:
130
+ results["directory_info"][dir_path] = {
131
+ "exists": False
132
+ }
133
+ except Exception as e:
134
+ results["directory_info"][dir_path] = {
135
+ "error": str(e)
136
+ }
137
+
138
+ return results
139
+
140
+ def run_debug_diagnostics(clash_path, config_path):
141
+ """运行调试诊断,并记录结果"""
142
+ logger.info("运行调试诊断...")
143
+
144
+ try:
145
+ results = check_clash_environment(clash_path, config_path)
146
+ logger.info("诊断完成")
147
+
148
+ # 将结果保存到文件
149
+ diagnostics_file = "clash_diagnostics.json"
150
+ with open(diagnostics_file, 'w', encoding='utf-8') as f:
151
+ json.dump(results, f, indent=2, ensure_ascii=False)
152
+ logger.info(f"诊断结果已保存到 {diagnostics_file}")
153
+
154
+ # 打印关键信息
155
+ if results["clash_binary"]["exists"]:
156
+ logger.info(f"Clash 二进制文件: {results['clash_binary']['path']}")
157
+ logger.info(f" - 大小: {results['clash_binary']['size']} 字节")
158
+ logger.info(f" - 权限: {results['clash_binary']['permissions']}")
159
+ logger.info(f" - 文件类型: {results['clash_binary']['file_type']}")
160
+ logger.info(f" - 可执行: {results['clash_binary'].get('is_executable', False)}")
161
+ else:
162
+ logger.error(f"Clash 二进制文件不存在: {results['clash_binary']['path']}")
163
+
164
+ if results["config_file"]["exists"]:
165
+ logger.info(f"配置文件: {results['config_file']['path']}")
166
+ logger.info(f" - 大小: {results['config_file']['size']} 字节")
167
+ preview = results["config_file"].get("content_preview", "")
168
+ if preview:
169
+ logger.info(f" - 内容预览: {preview[:100]}...")
170
+ else:
171
+ logger.error(f"配置文件不存在: {results['config_file']['path']}")
172
+
173
+ return results
174
+
175
+ except Exception as e:
176
+ logger.error(f"诊断过程中出错: {str(e)}")
177
+ return {"error": str(e)}
entrypoint.sh CHANGED
@@ -10,6 +10,24 @@ NC='\033[0m' # No Color
10
 
11
  echo "${GREEN}Starting Simple Clash Relay...${NC}"
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # 确保数据目录存在
14
  mkdir -p /app/data || echo "${YELLOW}Warning: Failed to create /app/data directory${NC}"
15
 
 
10
 
11
  echo "${GREEN}Starting Simple Clash Relay...${NC}"
12
 
13
+ # 添加诊断信息
14
+ echo "${YELLOW}System information:${NC}"
15
+ uname -a
16
+ echo "${YELLOW}Running as user:${NC}"
17
+ id
18
+
19
+ echo "${YELLOW}Checking Clash Core:${NC}"
20
+ file /app/clash_core/clash.meta-linux-amd64
21
+ ls -la /app/clash_core/clash.meta-linux-amd64
22
+ echo "${YELLOW}Checking Clash Core dependencies:${NC}"
23
+ ldd /app/clash_core/clash.meta-linux-amd64 || echo "ldd command failed"
24
+
25
+ echo "${YELLOW}Checking subconverter:${NC}"
26
+ file /app/subconverter/subconverter
27
+ ls -la /app/subconverter/subconverter
28
+ echo "${YELLOW}Checking subconverter dependencies:${NC}"
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}"
33