Deep-sea commited on
Commit
f214e3f
·
verified ·
1 Parent(s): 7e0ad38

Create Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +336 -0
Dockerfile ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM gitea/gitea:1.24.6
2
+
3
+ # 以 root 用户设置权限(Spaces 允许构建时 root)
4
+ USER root
5
+
6
+ # 安装 Python、pip、git 和异步库
7
+ RUN apk update && \
8
+ apk add python3 py3-pip git && \
9
+ pip3 install --break-system-packages --upgrade pip
10
+
11
+ # 安装 Python 包
12
+ RUN pip3 install --break-system-packages watchdog huggingface_hub aiohttp
13
+
14
+ # 预创建 Gitea 所需目录并设置权限
15
+ RUN mkdir -p /data/gitea/conf /data/gitea/log /data/gitea/git /data/git /data/ssh && \
16
+ chown -R git:git /data && \
17
+ chmod -R 770 /data
18
+
19
+ # 复制拉取 Hugging Face 数据集的脚本,硬编码 base64 解码的 HF_TOKEN
20
+ COPY --chown=root:root <<'EOF' /pullhf.py
21
+ import os
22
+ import shutil
23
+ from huggingface_hub import snapshot_download
24
+ import logging
25
+ import base64
26
+
27
+ # 配置日志
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format='%(asctime)s - %(levelname)s - %(message)s'
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+ def pull_from_hf_hub(repo_id, data_directory="/data"):
35
+ """从 Hugging Face Hub 数据集仓库拉取数据替换 /data 目录"""
36
+ # 硬编码的 base64 编码的 HF_TOKEN
37
+ hf_token_encoded = "aGZfcXllTEJnUUtPb2FUbHBMZ0FuTGFGTmJPV2xjUUtJT0VycQ=="
38
+ hf_token = base64.b64decode(hf_token_encoded).decode('utf-8')
39
+
40
+ try:
41
+ # 调试:打印 HF_TOKEN
42
+ logger.info(f"HF_TOKEN value: {hf_token}")
43
+ # 临时目录用于下载
44
+ temp_dir = "/tmp/hf_download"
45
+ if os.path.exists(temp_dir):
46
+ shutil.rmtree(temp_dir, ignore_errors=True)
47
+
48
+ # 下载 Hugging Face 数据集
49
+ logger.info(f"正在从 Hugging Face Hub 拉取数据集: {repo_id}")
50
+ snapshot_download(
51
+ repo_id=repo_id,
52
+ repo_type="dataset",
53
+ local_dir=temp_dir,
54
+ token=hf_token,
55
+ ignore_patterns=["*.tmp", "*.log", "*.temp", ".git/*"]
56
+ )
57
+
58
+ # 清空现有的 /data 目录
59
+ if os.path.exists(data_directory):
60
+ logger.info(f"正在清空现有 /data 目录: {data_directory}")
61
+ shutil.rmtree(data_directory, ignore_errors=True)
62
+
63
+ # 创建 /data 目录并移动下载的内容
64
+ os.makedirs(data_directory, exist_ok=True)
65
+ for item in os.listdir(temp_dir):
66
+ src = os.path.join(temp_dir, item)
67
+ dst = os.path.join(data_directory, item)
68
+ shutil.move(src, dst)
69
+
70
+ # 清理临时目录
71
+ shutil.rmtree(temp_dir, ignore_errors=True)
72
+
73
+ # 检查 /data/gitea 是否存在
74
+ gitea_dir = "/data/gitea"
75
+ if os.path.exists(gitea_dir):
76
+ logger.info(f"/data/gitea 存在,将跳过 Gitea 初始化页面")
77
+ # 设置环境变量文件以跳过初始化
78
+ with open("/data/gitea_skip_install", "w") as f:
79
+ f.write("true")
80
+ else:
81
+ logger.info(f"/data/gitea 不存在,允许 Gitea 进入初始化页面")
82
+ # 创建 Gitea 必需目录
83
+ for dir_path in ["/data/gitea/conf", "/data/gitea/log", "/data/gitea/git", "/data/git", "/data/ssh"]:
84
+ os.makedirs(dir_path, exist_ok=True)
85
+
86
+ # 修复权限:确保 /data 及其子目录为 git:git 所有且可写
87
+ logger.info("修复 /data 目录及其子目录的权限")
88
+ os.system("chown -R git:git /data")
89
+ os.system("chmod -R 770 /data")
90
+
91
+ # 调试:列出 /data 目录结构和权限
92
+ logger.info("调试:列出 /data 目录结构和权限")
93
+ os.system("ls -la /data")
94
+ os.system("ls -la /data/gitea 2>/dev/null || echo '/data/gitea 不存在'")
95
+ os.system("ls -la /data/gitea/conf 2>/dev/null || echo '/data/gitea/conf 不存在'")
96
+ os.system("ls -la /data/gitea/log 2>/dev/null || echo '/data/gitea/log 不存在'")
97
+
98
+ logger.info(f"✅ 成功从 Hugging Face Hub 拉取数据到 {data_directory}")
99
+ return True
100
+
101
+ except Exception as e:
102
+ logger.error(f"❌ 拉取 Hugging Face 数据集失败: {e}")
103
+ return False
104
+
105
+ if __name__ == "__main__":
106
+ repo_id = os.getenv("REPO_ID", "02engine/02gitea")
107
+ if not pull_from_hf_hub(repo_id):
108
+ logger.error("❌ 拉取数据集失败,退出")
109
+ exit(1)
110
+ EOF
111
+
112
+ # 复制上传脚本,移除检测到事件日志
113
+ COPY --chown=git:git <<'EOF' /uploadhf.py
114
+ import os
115
+ import time
116
+ import logging
117
+ import threading
118
+ import subprocess
119
+ import asyncio
120
+ import aiohttp
121
+ from pathlib import Path
122
+ from watchdog.observers import Observer
123
+ from watchdog.events import FileSystemEventHandler
124
+ from huggingface_hub import HfApi
125
+ import base64
126
+
127
+ # 配置日志
128
+ logging.basicConfig(
129
+ level=logging.INFO,
130
+ format='%(asctime)s - %(levelname)s - %(message)s'
131
+ )
132
+ logger = logging.getLogger(__name__)
133
+
134
+ class DataDirectoryHandler(FileSystemEventHandler):
135
+ """处理 /data 目录文件变化的监控器"""
136
+
137
+ def __init__(self, repo_id, hf_token, data_directory="/data"):
138
+ self.repo_id = repo_id
139
+ self.hf_token = hf_token
140
+ self.data_directory = data_directory
141
+ self.api = HfApi(token=hf_token)
142
+ self.last_commit_time = 0
143
+ self.commit_delay = 1 # 防抖延迟 1 秒
144
+ self.pending_changes = [] # 缓冲待上传变更
145
+ logger.info(f"初始化监控器,监控目录: {data_directory},目标仓库: {repo_id}")
146
+
147
+ def on_any_event(self, event):
148
+ """捕获所有文件系统事件"""
149
+ if event.is_directory:
150
+ return
151
+ self.pending_changes.append((event.event_type, event.src_path))
152
+ self.schedule_commit(f"文件{event.event_type}")
153
+
154
+ def schedule_commit(self, change_type):
155
+ """安排提交任务,带有防抖机制"""
156
+ current_time = time.time()
157
+ if current_time - self.last_commit_time > self.commit_delay:
158
+ self.last_commit_time = current_time
159
+ asyncio.run(self.commit_changes(change_type))
160
+
161
+ async def commit_changes(self, change_type):
162
+ """异步提交变更到 Hugging Face Hub,带重试机制"""
163
+ max_retries = 3
164
+ retry_delay = 5
165
+ change_summary = f"{change_type} ({len(self.pending_changes)} 文件)"
166
+ self.pending_changes = [] # 清空缓冲区
167
+ for attempt in range(max_retries):
168
+ try:
169
+ commit_message = f"自动提交: {change_summary} - {time.strftime('%Y-%m-%d %H:%M:%S')}"
170
+ logger.info(f"开始上传: {commit_message}")
171
+ await asyncio.to_thread(self.api.upload_folder,
172
+ folder_path=self.data_directory,
173
+ repo_id=self.repo_id,
174
+ repo_type="dataset",
175
+ commit_message=commit_message,
176
+ ignore_patterns=["*.tmp", "*.log", "*.temp", ".git/*"]
177
+ )
178
+ logger.info(f"✅ 成功提交变更到 Hugging Face Hub: {commit_message}")
179
+ return
180
+ except Exception as e:
181
+ logger.error(f"❌ 提交失败 (尝试 {attempt + 1}/{max_retries}): {e}")
182
+ if attempt < max_retries - 1:
183
+ logger.info(f"将在 {retry_delay} 秒后重试...")
184
+ await asyncio.sleep(retry_delay)
185
+ logger.error(f"❌ 达到最大重试次数,上传失败")
186
+
187
+ def start_directory_monitoring(data_directory="/data", repo_id=None, hf_token=None):
188
+ """启动目录监控服务"""
189
+ # 硬编码的 base64 编码的 HF_TOKEN
190
+ if not hf_token:
191
+ hf_token_encoded = "aGZfcXllTEJnUUtPb2FUbHBMZ0FuTGFGTmJPV2xjUUtJT0VycQ=="
192
+ hf_token = base64.b64decode(hf_token_encoded).decode('utf-8')
193
+ logger.info(f"HF_TOKEN value: {hf_token}")
194
+
195
+ if not repo_id:
196
+ raise ValueError("必须提供 repo_id 参数")
197
+
198
+ if not os.path.exists(data_directory):
199
+ logger.warning(f"监控目录 {data_directory} 不存在,正在创建...")
200
+ os.makedirs(data_directory, exist_ok=True)
201
+
202
+ event_handler = DataDirectoryHandler(
203
+ repo_id=repo_id,
204
+ hf_token=hf_token,
205
+ data_directory=data_directory
206
+ )
207
+
208
+ observer = Observer()
209
+ observer.schedule(event_handler, data_directory, recursive=True)
210
+ observer.start()
211
+ logger.info(f"🎯 目录监控服务已启动: {data_directory}")
212
+
213
+ return observer
214
+
215
+ def start_gitea_server(port=7860):
216
+ """启动 Gitea 服务器"""
217
+ def run_gitea():
218
+ try:
219
+ gitea_process = subprocess.Popen(
220
+ ['gitea', 'web', '--port', str(port)],
221
+ stdout=subprocess.PIPE,
222
+ stderr=subprocess.PIPE,
223
+ universal_newlines=True
224
+ )
225
+ logger.info(f"🚀 Gitea 服务器已启动,端口: {port}")
226
+ logger.info(f"📊 访问地址: http://localhost:{port}")
227
+
228
+ while True:
229
+ output = gitea_process.stdout.readline()
230
+ if output == '' and gitea_process.poll() is not None:
231
+ break
232
+ if output:
233
+ logger.info(f"Gitea: {output.strip()}")
234
+
235
+ return_code = gitea_process.poll()
236
+ if return_code != 0:
237
+ error_output = gitea_process.stderr.read()
238
+ logger.error(f"❌ Gitea 服务器异常退出,返回码: {return_code}")
239
+ logger.error(f"错误信息: {error_output}")
240
+ else:
241
+ logger.info("✅ Gitea 服务器正常退出")
242
+
243
+ except FileNotFoundError:
244
+ logger.error("❌ 未找到 gitea 命令,请确保 Gitea 已正确安装")
245
+ except Exception as e:
246
+ logger.error(f"❌ 启动 Gitea 服务器时发生错误: {e}")
247
+
248
+ gitea_thread = threading.Thread(target=run_gitea)
249
+ gitea_thread.daemon = True
250
+ gitea_thread.start()
251
+
252
+ return gitea_thread
253
+
254
+ async def main():
255
+ """主函数 - 启动 Gitea 和目录监控服务"""
256
+ # 配置参数
257
+ CONFIG = {
258
+ "data_directory": "/data",
259
+ "repo_id": os.getenv("REPO_ID", "02engine/02gitea"),
260
+ "hf_token": os.getenv('HF_TOKEN'),
261
+ "gitea_port": 7860
262
+ }
263
+
264
+ logger.info("🚀 启动集成服务...")
265
+
266
+ try:
267
+ # 先运行 pullhf.py 拉取最新数据集
268
+ logger.info("运行 pullhf.py 拉取最新数据集")
269
+ pull_result = subprocess.run(["python3", "/pullhf.py"], check=True)
270
+ if pull_result.returncode != 0:
271
+ logger.error("❌ pullhf.py 执行失败,退出")
272
+ exit(1)
273
+
274
+ # 启动 Gitea 服务器
275
+ gitea_thread = start_gitea_server(CONFIG["gitea_port"])
276
+
277
+ # 启动目录监控服务
278
+ observer = start_directory_monitoring(
279
+ data_directory=CONFIG["data_directory"],
280
+ repo_id=CONFIG["repo_id"],
281
+ hf_token=CONFIG["hf_token"]
282
+ )
283
+
284
+ logger.info("✅ 所有服务已启动完成!")
285
+ logger.info("📁 目录监控: /data → Hugging Face Hub")
286
+ logger.info("🌐 Gitea 服务: http://localhost:7860")
287
+ logger.info("🛑 按 Ctrl+C 停止所有服务")
288
+
289
+ try:
290
+ while True:
291
+ await asyncio.sleep(1)
292
+ except KeyboardInterrupt:
293
+ logger.info("正在停止服务...")
294
+
295
+ except Exception as e:
296
+ logger.error(f"❌ 启动服务时发生错误: {e}")
297
+ finally:
298
+ if 'observer' in locals():
299
+ observer.stop()
300
+ observer.join()
301
+ logger.info("所有服务已停止")
302
+
303
+ if __name__ == "__main__":
304
+ asyncio.run(main())
305
+ EOF
306
+
307
+ # 设置工作目录
308
+ WORKDIR /data
309
+
310
+ # 暴露 Spaces 默认端口(文档用途,实际由 $PORT 控制)
311
+ EXPOSE 7860
312
+
313
+ # Gitea 环境变量:适配 Spaces 端口,禁用 SSH,动态决定是否跳过安装页面
314
+ RUN echo '#!/bin/sh' > /set_gitea_env.sh && \
315
+ echo 'if [ -f /data/gitea_skip_install ]; then' >> /set_gitea_env.sh && \
316
+ echo ' export GITEA__server__STARTUP_DISABLE_INSTALL_FORM=true' >> /set_gitea_env.sh && \
317
+ echo 'else' >> /set_gitea_env.sh && \
318
+ echo ' export GITEA__server__STARTUP_DISABLE_INSTALL_FORM=false' >> /set_gitea_env.sh && \
319
+ echo 'fi' >> /set_gitea_env.sh && \
320
+ echo 'exec "$@"' >> /set_gitea_env.sh && \
321
+ chmod +x /set_gitea_env.sh
322
+
323
+ ENV GITEA__database__DB_TYPE=sqlite3 \
324
+ GITEA__database__PATH=/data/gitea/gitea.db \
325
+ GITEA__server__ROOT_URL=https://deep-sea-02gitea.hf.space/ \
326
+ GITEA__server__HTTP_ADDR=0.0.0.0 \
327
+ GITEA__server__HTTP_PORT=7860 \
328
+ GITEA__server__DISABLE_SSH=true \
329
+ USER=git
330
+
331
+ # 切换到 git 用户(非 root)
332
+ USER git
333
+
334
+ # 运行 Gitea,显式指定 Spaces 默认端口
335
+ RUN cat /uploadhf.py
336
+ CMD ["/set_gitea_env.sh", "python3", "/uploadhf.py"]