| FROM gitea/gitea:1.24.6 |
|
|
| |
| USER root |
|
|
| |
| RUN apk update && \ |
| apk add python3 py3-pip git && \ |
| pip3 install --break-system-packages --upgrade pip |
|
|
| |
| RUN pip3 install --break-system-packages watchdog huggingface_hub aiohttp pytz |
|
|
| |
| RUN mkdir -p /data/gitea/conf /data/gitea/log /data/gitea/git /data/git /data/ssh && \ |
| chown -R git:git /data && \ |
| chmod -R 770 /data |
|
|
| |
| COPY --chown=root:root <<'EOF' /pullhf.py |
| import os |
| import shutil |
| from huggingface_hub import snapshot_download |
| import logging |
| import base64 |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(levelname)s - %(message)s' |
| ) |
| logger = logging.getLogger(__name__) |
|
|
| def pull_from_hf_hub(repo_id, data_directory="/data"): |
| """从 Hugging Face Hub 数据集仓库拉取数据替换 /data 目录""" |
| # 硬编码的 base64 编码的 HF_TOKEN |
| hf_token_encoded = "aGZfcXllTEJnUUtPb2FUbHBMZ0FuTGFGTmJPV2xjUUtJT0VycQ==" |
| hf_token = base64.b64decode(hf_token_encoded).decode('utf-8') |
| |
| try: |
| # 调试:打印 HF_TOKEN |
| logger.info(f"HF_TOKEN value: {hf_token}") |
| # 临时目录用于下载 |
| temp_dir = "/tmp/hf_download" |
| if os.path.exists(temp_dir): |
| shutil.rmtree(temp_dir, ignore_errors=True) |
| |
| # 下载 Hugging Face 数据集 |
| logger.info(f"正在从 Hugging Face Hub 拉取数据集: {repo_id}") |
| snapshot_download( |
| repo_id=repo_id, |
| repo_type="dataset", |
| local_dir=temp_dir, |
| token=hf_token, |
| ignore_patterns=["*.tmp", "*.log", "*.temp", ".git/*"] |
| ) |
|
|
| # 清空现有的 /data 目录 |
| if os.path.exists(data_directory): |
| logger.info(f"正在清空现有 /data 目录: {data_directory}") |
| shutil.rmtree(data_directory, ignore_errors=True) |
| |
| # 创建 /data 目录并移动下载的内容 |
| os.makedirs(data_directory, exist_ok=True) |
| for item in os.listdir(temp_dir): |
| src = os.path.join(temp_dir, item) |
| dst = os.path.join(data_directory, item) |
| shutil.move(src, dst) |
| |
| # 清理临时目录 |
| shutil.rmtree(temp_dir, ignore_errors=True) |
| |
| # 检查 /data/gitea 是否存在 |
| gitea_dir = "/data/gitea" |
| if os.path.exists(gitea_dir): |
| logger.info(f"/data/gitea 存在,将跳过 Gitea 初始化页面") |
| # 设置环境变量文件以跳过初始化 |
| with open("/data/gitea_skip_install", "w") as f: |
| f.write("true") |
| else: |
| logger.info(f"/data/gitea 不存在,允许 Gitea 进入初始化页面") |
| # 创建 Gitea 必需目录 |
| for dir_path in ["/data/gitea/conf", "/data/gitea/log", "/data/gitea/git", "/data/git", "/data/ssh"]: |
| os.makedirs(dir_path, exist_ok=True) |
| |
| # 修复权限:确保 /data 及其子目录为 git:git 所有且可写 |
| logger.info("修复 /data 目录及其子目录的权限") |
| os.system("chown -R git:git /data") |
| os.system("chmod -R 770 /data") |
| |
| # 调试:列出 /data 目录结构和权限 |
| logger.info("调试:列出 /data 目录结构和权限") |
| os.system("ls -la /data") |
| os.system("ls -la /data/gitea 2>/dev/null || echo '/data/gitea 不存在'") |
| os.system("ls -la /data/gitea/conf 2>/dev/null || echo '/data/gitea/conf 不存在'") |
| os.system("ls -la /data/gitea/log 2>/dev/null || echo '/data/gitea/log 不存在'") |
| |
| logger.info(f"✅ 成功从 Hugging Face Hub 拉取数据到 {data_directory}") |
| return True |
|
|
| except Exception as e: |
| logger.error(f"❌ 拉取 Hugging Face 数据集失败: {e}") |
| return False |
|
|
| if __name__ == "__main__": |
| repo_id = os.getenv("REPO_ID", "02engine/02gitea") |
| if not pull_from_hf_hub(repo_id): |
| logger.error("❌ 拉取数据集失败,退出") |
| exit(1) |
| EOF |
|
|
| |
| COPY --chown=git:git <<'EOF' /uploadhf.py |
| import os |
| import time |
| import logging |
| import threading |
| import subprocess |
| import asyncio |
| import aiohttp |
| from pathlib import Path |
| from watchdog.observers import Observer |
| from watchdog.events import FileSystemEventHandler |
| from huggingface_hub import HfApi |
| import base64 |
| from datetime import datetime |
| import pytz |
|
|
| |
| beijing_tz = pytz.timezone('Asia/Shanghai') |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(levelname)s - %(message)s', |
| datefmt='%Y-%m-%d %H:%M:%S %Z', |
| handlers=[ |
| logging.StreamHandler() |
| ] |
| ) |
| |
| logging.Formatter.converter = lambda *args: datetime.now(beijing_tz).timetuple() |
| logger = logging.getLogger(__name__) |
|
|
| class DataDirectoryHandler(FileSystemEventHandler): |
| """处理 /data 目录文件变化的监控器""" |
| |
| def __init__(self, repo_id, hf_token, data_directory="/data"): |
| self.repo_id = repo_id |
| self.hf_token = hf_token |
| self.data_directory = data_directory |
| self.api = HfApi(token=hf_token) |
| self.last_commit_time = 0 |
| self.commit_delay = 1 # 防抖延迟 1 秒 |
| self.pending_changes = [] # 缓冲待上传变更 |
| logger.info(f"初始化监控器,监控目录: {data_directory},目标仓库: {repo_id}") |
| |
| def on_any_event(self, event): |
| """捕获所有文件系统事件""" |
| if event.is_directory: |
| return |
| self.pending_changes.append((event.event_type, event.src_path)) |
| self.schedule_commit(f"文件{event.event_type}") |
|
|
| def schedule_commit(self, change_type): |
| """安排提交任务,带有防抖机制""" |
| current_time = time.time() |
| if current_time - self.last_commit_time > self.commit_delay: |
| self.last_commit_time = current_time |
| # 使用 asyncio.create_task 异步调度提交 |
| asyncio.create_task(self.commit_changes(change_type)) |
|
|
| async def commit_changes(self, change_type): |
| """异步提交变更到 Hugging Face Hub,带重试机制""" |
| max_retries = 3 |
| retry_delay = 5 |
| change_summary = f"{change_type} ({len(self.pending_changes)} 文件)" |
| self.pending_changes = [] # 清空缓冲区 |
| for attempt in range(max_retries): |
| try: |
| commit_message = f"自动提交: {change_summary} - {datetime.now(beijing_tz).strftime('%Y-%m-%d %H:%M:%S')}" |
| logger.info(f"开始上传: {commit_message}") |
| await asyncio.to_thread(self.api.upload_folder, |
| folder_path=self.data_directory, |
| repo_id=self.repo_id, |
| repo_type="dataset", |
| commit_message=commit_message, |
| ignore_patterns=["*.tmp", "*._workers", "*.log", "*.temp", ".git/*"] |
| ) |
| logger.info(f"✅ 成功提交变更到 Hugging Face Hub: {commit_message}") |
| return |
| except Exception as e: |
| logger.error(f"❌ 提交失败 (尝试 {attempt + 1}/{max_retries}): {e}") |
| if attempt < max_retries - 1: |
| logger.info(f"将在 {retry_delay} 秒后重试...") |
| await asyncio.sleep(retry_delay) |
| logger.error(f"❌ 达到最大重试次数,上传失败") |
|
|
| def start_directory_monitoring(data_directory="/data", repo_id=None, hf_token=None): |
| """启动目录监控服务""" |
| # 硬编码的 base64 编码的 HF_TOKEN |
| if not hf_token: |
| hf_token_encoded = "aGZfcXllTEJnUUtPb2FUbHBMZ0FuTGFGTmJPV2xjUUtJT0VycQ==" |
| hf_token = base64.b64decode(hf_token_encoded).decode('utf-8') |
| logger.info(f"HF_TOKEN value: {hf_token}") |
| |
| if not repo_id: |
| raise ValueError("必须提供 repo_id 参数") |
| |
| if not os.path.exists(data_directory): |
| logger.warning(f"监控目录 {data_directory} 不存在,正在创建...") |
| os.makedirs(data_directory, exist_ok=True) |
| |
| event_handler = DataDirectoryHandler( |
| repo_id=repo_id, |
| hf_token=hf_token, |
| data_directory=data_directory |
| ) |
| |
| observer = Observer() |
| observer.schedule(event_handler, data_directory, recursive=True) |
| observer.start() |
| logger.info(f"🎯 目录监控服务已启动: {data_directory}") |
| |
| return observer |
|
|
| def start_gitea_server(port=7860): |
| """启动 Gitea 服务器,包含自动重试逻辑和超时机制,达到重试上限时抛出错误""" |
| def run_gitea(): |
| max_restart_attempts = 5 |
| restart_delay = 10 # 每次重启前的等待时间(秒) |
| startup_timeout = 30 # 启动超时时间(秒) |
| attempt = 0 |
|
|
| # 检查 /data 目录权限和数据库状态 |
| logger.info("检查 /data 目录权限和 Gitea 数据库状态") |
| os.system("ls -la /data 2>/dev/null") |
| os.system("ls -la /data/gitea 2>/dev/null || echo '/data/gitea 不存在'") |
| if os.path.exists("/data/gitea/gitea.db"): |
| logger.info("Gitea 数据库文件存在: /data/gitea/gitea.db") |
| else: |
| logger.warning("Gitea 数据库文件不存在: /data/gitea/gitea.db") |
|
|
| while attempt < max_restart_attempts: |
| try: |
| # 启动 Gitea 进程 |
| gitea_process = subprocess.Popen( |
| ['gitea', 'web', '--port', str(port)], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| universal_newlines=True |
| ) |
| logger.info(f"🚀 Gitea 服务器启动尝试 {attempt + 1}/{max_restart_attempts},端口: {port}") |
| logger.info(f"📊 访问地址: http://localhost:{port}") |
|
|
| # 等待 Gitea 启动(超时机制) |
| start_time = time.time() |
| while time.time() - start_time < startup_timeout: |
| if gitea_process.poll() is not None: |
| break |
| line = gitea_process.stderr.readline() |
| if line: |
| logger.info(f"Gitea stderr: {line.strip()}") |
| time.sleep(0.1) |
|
|
| # 检查进程状态 |
| return_code = gitea_process.poll() |
| if return_code is not None: |
| # 进程已退出 |
| error_output = gitea_process.stderr.read() |
| logger.error(f"❌ Gitea 服务器异常退出,返回码: {return_code}") |
| if error_output: |
| logger.error(f"错误信息: {error_output.strip()}") |
| attempt += 1 |
| if attempt < max_restart_attempts: |
| logger.info(f"将在 {restart_delay} 秒后尝试重启 Gitea (尝试 {attempt + 1}/{max_restart_attempts})") |
| time.sleep(restart_delay) |
| else: |
| error_msg = f"❌ 达到最大重试次数 ({max_restart_attempts}),Gitea 启动失败" |
| logger.error(error_msg) |
| raise RuntimeError(error_msg) |
| else: |
| # 进程仍在运行,开始读取输出 |
| logger.info("✅ Gitea 服务器启动成功,开始监控输出") |
| while True: |
| output = gitea_process.stdout.readline() |
| if output == '' and gitea_process.poll() is not None: |
| break |
| if output: |
| logger.info(f"Gitea: {output.strip()}") |
| |
| return_code = gitea_process.poll() |
| if return_code != 0: |
| error_output = gitea_process.stderr.read() |
| logger.error(f"❌ Gitea 服务器后期异常退出,返回码: {return_code}") |
| if error_output: |
| logger.error(f"错误信息: {error_output.strip()}") |
| attempt += 1 |
| if attempt < max_restart_attempts: |
| logger.info(f"将在 {restart_delay} 秒后尝试重启 Gitea (尝试 {attempt + 1}/{max_restart_attempts})") |
| time.sleep(restart_delay) |
| else: |
| error_msg = f"❌ 达到最大重试次数 ({max_restart_attempts}),Gitea 启动失败" |
| logger.error(error_msg) |
| raise RuntimeError(error_msg) |
| else: |
| logger.info("✅ Gitea 服务器正常退出") |
| break |
| |
| except FileNotFoundError: |
| error_msg = "❌ 未找到 gitea 命令,请确保 Gitea 已正确安装" |
| logger.error(error_msg) |
| raise RuntimeError(error_msg) |
| except Exception as e: |
| logger.error(f"❌ 启动 Gitea 服务器时发生错误: {e}") |
| attempt += 1 |
| if attempt < max_restart_attempts: |
| logger.info(f"将在 {restart_delay} 秒后尝试重启 Gitea (尝试 {attempt + 1}/{max_restart_attempts})") |
| time.sleep(restart_delay) |
| else: |
| error_msg = f"❌ 达到最大重试次数 ({max_restart_attempts}),Gitea 启动失败: {e}" |
| logger.error(error_msg) |
| raise RuntimeError(error_msg) |
| |
| gitea_thread = threading.Thread(target=run_gitea) |
| gitea_thread.daemon = True |
| gitea_thread.start() |
| |
| return gitea_thread |
|
|
| async def main(): |
| """主函数 - 启动 Gitea 和目录监控服务""" |
| # 配置参数 |
| CONFIG = { |
| "data_directory": "/data", |
| "repo_id": os.getenv("REPO_ID", "02engine/02gitea"), |
| "hf_token": os.getenv('HF_TOKEN'), |
| "gitea_port": 7860 |
| } |
| |
| logger.info("🚀 启动集成服务...") |
| |
| try: |
| # 先运行 pullhf.py 拉取最新数据集 |
| logger.info("运行 pullhf.py 拉取最新数据集") |
| pull_result = subprocess.run(["python3", "/pullhf.py"], check=True) |
| if pull_result.returncode != 0: |
| logger.error("❌ pullhf.py 执行失败,退出") |
| exit(1) |
| |
| # 启动 Gitea 服务器 |
| gitea_thread = start_gitea_server(CONFIG["gitea_port"]) |
| |
| # 启动目录监控服务 |
| observer = start_directory_monitoring( |
| data_directory=CONFIG["data_directory"], |
| repo_id=CONFIG["repo_id"], |
| hf_token=CONFIG["hf_token"] |
| ) |
| |
| logger.info("✅ 所有服务已启动完成!") |
| logger.info("📁 目录监控: /data → Hugging Face Hub") |
| logger.info(f"🌐 Gitea 服务: http://localhost:{CONFIG['gitea_port']}") |
| logger.info("🛑 按 Ctrl+C 停止所有服务") |
| |
| try: |
| while True: |
| await asyncio.sleep(1) |
| except KeyboardInterrupt: |
| logger.info("正在停止服务...") |
| |
| except Exception as e: |
| logger.error(f"❌ 启动服务时发生错误: {e}") |
| raise # 重新抛出异常以确保容器退出 |
| finally: |
| if 'observer' in locals(): |
| observer.stop() |
| observer.join() |
| logger.info("所有服务已停止") |
|
|
| if __name__ == "__main__": |
| asyncio.run(main()) |
| EOF |
|
|
| |
| WORKDIR /data |
|
|
| |
| EXPOSE 7860 |
|
|
| |
| RUN echo '#!/bin/sh' > /set_gitea_env.sh && \ |
| echo 'if [ -f /data/gitea_skip_install ]; then' >> /set_gitea_env.sh && \ |
| echo ' export GITEA__server__STARTUP_DISABLE_INSTALL_FORM=true' >> /set_gitea_env.sh && \ |
| echo 'else' >> /set_gitea_env.sh && \ |
| echo ' export GITEA__server__STARTUP_DISABLE_INSTALL_FORM=false' >> /set_gitea_env.sh && \ |
| echo 'fi' >> /set_gitea_env.sh && \ |
| echo 'exec "$@"' >> /set_gitea_env.sh && \ |
| chmod +x /set_gitea_env.sh |
|
|
| ENV GITEA__database__DB_TYPE=sqlite3 \ |
| GITEA__database__PATH=/data/gitea/gitea.db \ |
| GITEA__server__ROOT_URL=https://deep-sea-02gitea.hf.space/ \ |
| GITEA__server__HTTP_ADDR=0.0.0.0 \ |
| GITEA__server__HTTP_PORT=7860 \ |
| GITEA__server__DISABLE_SSH=true \ |
| USER=git |
|
|
| |
| USER git |
|
|
| |
| CMD ["/set_gitea_env.sh", "python3", "/uploadhf.py"] |