clash-linux commited on
Commit
7a89f6b
·
verified ·
1 Parent(s): 4528303

Upload 21 files

Browse files
Files changed (2) hide show
  1. Dockerfile +25 -31
  2. app/main.py +97 -8
Dockerfile CHANGED
@@ -34,21 +34,24 @@ RUN mkdir -p ./clash_core ./subconverter ./data && \
34
  chmod -R 777 ./clash_core && \
35
  chmod -R 777 ./subconverter
36
 
37
- # 下载并安装Clash Meta,保留原始文件名
38
- RUN echo "Downloading Clash Meta..." && \
39
- curl -L -f -o /tmp/clash-meta.gz "https://github.com/MetaCubeX/Clash.Meta/releases/download/v1.16.0/clash.meta-linux-amd64-v1.16.0.gz" && \
40
- echo "Extracting Clash Meta..." && \
41
- gunzip -c /tmp/clash-meta.gz > ./clash_core/clash.meta-linux-amd64 && \
42
- echo "Setting Clash Meta permissions..." && \
43
- chmod +x ./clash_core/clash.meta-linux-amd64 && \
44
- # 确保Linux可执行属性已设置
45
- ls -la ./clash_core/clash.meta-linux-amd64 && \
46
- # 显示文件类型
47
- file ./clash_core/clash.meta-linux-amd64 && \
48
- echo "Verifying Clash Meta exists..." && \
49
- test -f ./clash_core/clash.meta-linux-amd64 && \
50
- echo "Cleaning up Clash Meta download..." && \
51
- rm /tmp/clash-meta.gz
 
 
 
52
 
53
  # 下载并完整解压subconverter
54
  RUN echo "Downloading subconverter..." && \
@@ -83,21 +86,12 @@ RUN echo "Installing Python dependencies..." && \
83
  # 可选:删除构建依赖以减小镜像体积
84
  RUN apk del python3-dev musl-dev libffi-dev yaml-dev
85
 
86
- # 下载并准备 Yacd UI 文件 (从 Yacd-meta 的 gh-pages 分支)
87
- RUN echo "Downloading Yacd-meta UI (gh-pages branch)..." && \
88
- YACD_DIR=/app/app/static/yacd && \
89
- mkdir -p ${YACD_DIR} && \
90
- # 下载 gh-pages 分支的 zip 压缩包
91
- curl -L -f -o /tmp/yacd-gh-pages.zip "https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip" && \
92
- echo "Extracting Yacd-meta UI (gh-pages)..." && \
93
- # 解压到临时目录
94
- unzip -q /tmp/yacd-gh-pages.zip -d /tmp && \
95
- # 将解压后的 gh-pages 目录下的 *所有内容* 移动到目标位置
96
- # 注意:解压后的文件夹名通常是 {repo_name}-{branch_name},即 Yacd-meta-gh-pages
97
- mv /tmp/Yacd-meta-gh-pages/* ${YACD_DIR}/ && \
98
- echo "Cleaning up Yacd-meta download..." && \
99
- rm /tmp/yacd-gh-pages.zip && \
100
- rm -rf /tmp/Yacd-meta-gh-pages
101
 
102
  # 设置环境变量
103
  ENV PYTHONDONTWRITEBYTECODE=1 \
@@ -117,7 +111,7 @@ COPY entrypoint.sh ./
117
  RUN chmod +x ./entrypoint.sh
118
 
119
  # 给脚本和二进制文件执行权限 (重复的chmod可能不需要,但在构建阶段设置更安全)
120
- RUN chmod +x ./clash_core/clash.meta-linux-amd64 || true
121
  RUN chmod +x ./subconverter/subconverter || true
122
 
123
  # 暴露端口
 
34
  chmod -R 777 ./clash_core && \
35
  chmod -R 777 ./subconverter
36
 
37
+ # Download Clash Meta Core
38
+ # Check https://github.com/MetaCubeX/Clash.Meta/releases for latest versions and architectures
39
+ ARG CLASH_META_VERSION="v1.18.0" # Or specific version like v1.18.0
40
+ ARG TARGETARCH
41
+
42
+ # Determine Clash binary URL based on architecture
43
+ RUN if [ "${TARGETARCH}" = "amd64" ]; then \
44
+ CLASH_URL="https://github.com/MetaCubeX/Clash.Meta/releases/download/${CLASH_META_VERSION}/clash.meta-linux-amd64-${CLASH_META_VERSION}.gz"; \
45
+ elif [ "${TARGETARCH}" = "arm64" ]; then \
46
+ CLASH_URL="https://github.com/MetaCubeX/Clash.Meta/releases/download/${CLASH_META_VERSION}/clash.meta-linux-arm64-${CLASH_META_VERSION}.gz"; \
47
+ else \
48
+ echo "Unsupported architecture: ${TARGETARCH}" && exit 1; \
49
+ fi && \
50
+ echo "Downloading Clash Meta Core from ${CLASH_URL}" && \
51
+ curl -sSL ${CLASH_URL} -o clash.meta.gz && \
52
+ gzip -d clash.meta.gz && \
53
+ mv clash.meta /app/clash_core/clash.meta-linux-${TARGETARCH} && \
54
+ chmod +x /app/clash_core/clash.meta-linux-${TARGETARCH} # Add execute permission here
55
 
56
  # 下载并完整解压subconverter
57
  RUN echo "Downloading subconverter..." && \
 
86
  # 可选:删除构建依赖以减小镜像体积
87
  RUN apk del python3-dev musl-dev libffi-dev yaml-dev
88
 
89
+ # Download Yacd UI (gh-pages branch)
90
+ RUN echo "Downloading Yacd UI from gh-pages branch..." && \
91
+ curl -sSL https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.tar.gz -o yacd.tar.gz && \
92
+ mkdir -p /app/static/yacd && \
93
+ tar -xzf yacd.tar.gz --strip-components=1 -C /app/static/yacd && \
94
+ rm yacd.tar.gz
 
 
 
 
 
 
 
 
 
95
 
96
  # 设置环境变量
97
  ENV PYTHONDONTWRITEBYTECODE=1 \
 
111
  RUN chmod +x ./entrypoint.sh
112
 
113
  # 给脚本和二进制文件执行权限 (重复的chmod可能不需要,但在构建阶段设置更安全)
114
+ RUN chmod +x ./clash_core/clash.meta-linux-${TARGETARCH} || true
115
  RUN chmod +x ./subconverter/subconverter || true
116
 
117
  # 暴露端口
app/main.py CHANGED
@@ -13,6 +13,7 @@ from .sub_manager import SubscriptionManager
13
  from .auth import authenticate
14
  import requests
15
  from functools import wraps
 
16
 
17
  # 配置日志
18
  logging.basicConfig(
@@ -21,21 +22,109 @@ logging.basicConfig(
21
  )
22
  logger = logging.getLogger(__name__)
23
 
24
- # 从环境变量加载配置
25
- SUB_URL = os.environ.get("SUB_URL")
26
- API_KEY = os.environ.get("API_KEY", "changeme")
 
 
 
27
  FLASK_PORT = int(os.environ.get("FLASK_PORT", 7860)) # 默认端口改为7860
28
  CLASH_PROXY_PORT = int(os.environ.get("CLASH_PROXY_PORT", 7890))
29
  CLASH_API_PORT = int(os.environ.get("CLASH_API_PORT", 9090))
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  # 初始化Flask应用
32
  app = Flask(__name__, static_folder='static')
33
 
34
- # 初始化管理器
35
- clash_manager = None
36
- sub_manager = None
37
- initialization_error = None
38
-
39
  @app.before_request
40
  def initialize_once():
41
  """应用首次请求前的初始化"""
 
13
  from .auth import authenticate
14
  import requests
15
  from functools import wraps
16
+ from threading import Lock
17
 
18
  # 配置日志
19
  logging.basicConfig(
 
22
  )
23
  logger = logging.getLogger(__name__)
24
 
25
+ # --- 全局变量 ---
26
+ clash_manager = None
27
+ sub_manager = None
28
+ initialization_error = "尚未初始化" # 初始状态
29
+ API_KEY = os.environ.get("API_KEY", "changeme") # 从环境变量获取API密钥,默认changeme
30
+ SUB_URL = os.environ.get("SUB_URL") # 从环境变量获取订阅链接
31
  FLASK_PORT = int(os.environ.get("FLASK_PORT", 7860)) # 默认端口改为7860
32
  CLASH_PROXY_PORT = int(os.environ.get("CLASH_PROXY_PORT", 7890))
33
  CLASH_API_PORT = int(os.environ.get("CLASH_API_PORT", 9090))
34
 
35
+ # 基础路径
36
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
37
+ DATA_DIR = os.path.join(BASE_DIR, "data")
38
+ CLASH_CORE_DIR = os.path.join(BASE_DIR, "clash_core")
39
+ CONFIG_PATH = os.path.join(DATA_DIR, "config.yaml")
40
+ RAW_CONFIG_PATH = f"{CONFIG_PATH}.raw"
41
+ CLASH_BINARY_PATH = os.path.join(CLASH_CORE_DIR, "clash.meta-linux-amd64") # 根据实际文件名调整
42
+
43
+ # 确保目录存在
44
+ os.makedirs(DATA_DIR, exist_ok=True)
45
+ os.makedirs(CLASH_CORE_DIR, exist_ok=True)
46
+
47
+ class InitializationManager:
48
+ def __init__(self):
49
+ self._initialized = False
50
+ self._lock = Lock()
51
+
52
+ def __call__(self):
53
+ global clash_manager, sub_manager, initialization_error
54
+
55
+ # 使用锁确保只初始化一次
56
+ with self._lock:
57
+ if self._initialized:
58
+ return # 如果已经初始化,直接返回
59
+
60
+ logger.info("正在初始化应用 (首次请求或重新初始化)...")
61
+ initialization_error = "正在初始化..." # 设置状态
62
+
63
+ try:
64
+ # 优先检查本地配置文件是否存在
65
+ if os.path.exists(CONFIG_PATH):
66
+ logger.info(f"找到本地配置文件: {CONFIG_PATH},优先使用此配置")
67
+ try:
68
+ # 验证并可能需要修补现有配置
69
+ temp_sub_manager = SubscriptionManager(config_path=CONFIG_PATH)
70
+ temp_sub_manager._patch_config() # 确保端口等设置正确
71
+ logger.info("已检查并修补本地配置文件")
72
+
73
+ # 使用本地配置启动Clash
74
+ clash_manager = ClashManager(
75
+ config_path=CONFIG_PATH,
76
+ clash_path=CLASH_BINARY_PATH,
77
+ api_port=CLASH_API_PORT,
78
+ proxy_port=CLASH_PROXY_PORT
79
+ )
80
+ clash_manager.start_clash()
81
+ initialization_error = None # 初始化成功
82
+ self._initialized = True
83
+ logger.info("使用本地配置成功初始化并启动Clash")
84
+ return # 初始化成功,退出
85
+ except Exception as e:
86
+ error_msg = f"使用本地配置文件启动Clash失败: {str(e)}"
87
+ logger.error(error_msg)
88
+ initialization_error = error_msg
89
+ # 不要在这里返回,继续尝试下载订阅(如果提供了URL)
90
+
91
+ # 如果本地配置不存在或启动失败,并且提供了订阅URL,则尝试下载
92
+ if SUB_URL:
93
+ logger.info("未找到本地配置或启动失败,尝试从订阅URL加载...")
94
+ sub_manager = SubscriptionManager(sub_url=SUB_URL, config_path=CONFIG_PATH)
95
+ sub_manager.load_and_convert_sub() # 下载并转换
96
+
97
+ clash_manager = ClashManager(
98
+ config_path=CONFIG_PATH,
99
+ clash_path=CLASH_BINARY_PATH,
100
+ api_port=CLASH_API_PORT,
101
+ proxy_port=CLASH_PROXY_PORT
102
+ )
103
+ clash_manager.start_clash()
104
+ initialization_error = None # 初始化成功
105
+ logger.info("成功从订阅URL初始化并启动Clash")
106
+
107
+ # 如果没有本地配置,也没有提供订阅URL
108
+ elif not os.path.exists(CONFIG_PATH):
109
+ error_msg = "初始化失败:未找到本地 config.yaml 且未设置 SUB_URL 环境变量"
110
+ logger.error(error_msg)
111
+ initialization_error = error_msg
112
+
113
+ # 如果走到这里,表示初始化完成(可能成功也可能失败)
114
+ self._initialized = True
115
+
116
+ except Exception as e:
117
+ error_msg = f"应用初始化过程中���生严重错误: {str(e)}"
118
+ logger.exception(error_msg) # 使用exception记录堆栈信息
119
+ initialization_error = error_msg
120
+ # 即使失败,也标记为已尝试初始化,避免无限重试
121
+ self._initialized = True
122
+
123
+ initialize_once = InitializationManager()
124
+
125
  # 初始化Flask应用
126
  app = Flask(__name__, static_folder='static')
127
 
 
 
 
 
 
128
  @app.before_request
129
  def initialize_once():
130
  """应用首次请求前的初始化"""