FROM gitea/gitea:1.24.6 # 以 root 用户设置权限(Spaces 允许构建时 root) USER root # 安装 Python 和 pip RUN apk update && \ apk add python3 py3-pip && \ pip3 install --break-system-packages --upgrade pip # 安装 Python 包 RUN pip3 install --break-system-packages watchdog huggingface_hub # 确保数据目录权限,使用默认 git 用户 (UID/GID 1000) RUN mkdir -p /data/gitea /data/git /data/ssh && \ chown -R git:git /data && \ chmod -R 750 /data # 设置工作目录 WORKDIR /data # 暴露 Spaces 默认端口(文档用途,实际由 $PORT 控制) EXPOSE 7860 # Gitea 环境变量:适配 Spaces 端口,禁用 SSH 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 # 切换到 git 用户(非 root) USER git COPY --chown=git:git <<'EOF' /uploadhf.py import os import time import logging import threading import subprocess from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from huggingface_hub import HfApi # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) 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 = 30 # 防抖延迟:30秒内多次变动只提交一次 logger.info(f"初始化监控器,监控目录: {data_directory},目标仓库: {repo_id}") def on_modified(self, event): """文件被修改时触发""" if not event.is_directory: logger.info(f"检测到文件修改: {event.src_path}") self.schedule_commit("文件修改") def on_created(self, event): """文件被创建时触发""" if not event.is_directory: logger.info(f"检测到新文件: {event.src_path}") self.schedule_commit("文件创建") def on_deleted(self, event): """文件被删除时触发""" if not event.is_directory: logger.info(f"检测到文件删除: {event.src_path}") self.schedule_commit("文件删除") def on_moved(self, event): """文件被移动或重命名时触发""" logger.info(f"检测到文件移动: {event.src_path} -> {event.dest_path}") self.schedule_commit("文件移动") 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 # 使用线程执行提交,避免阻塞监控 thread = threading.Thread(target=self.commit_changes, args=(change_type,)) thread.daemon = True thread.start() def commit_changes(self, change_type): """提交变更到 Hugging Face Hub""" try: commit_message = f"自动提交: {change_type} - {time.strftime('%Y-%m-%d %H:%M:%S')}" # 上传整个目录 self.api.upload_folder( folder_path=self.data_directory, repo_id=self.repo_id, repo_type="dataset", commit_message=commit_message, ignore_patterns=["*.tmp", "*.log", "*.temp", ".git/*"] # 忽略临时文件 ) logger.info(f"✅ 成功提交变更到 Hugging Face Hub: {commit_message}") except Exception as e: logger.error(f"❌ 提交失败: {e}") def start_directory_monitoring(data_directory="/data", repo_id=None, hf_token=None): """启动目录监控服务""" # 参数验证 if not hf_token: hf_token = os.getenv('HF_TOKEN') if not hf_token: raise ValueError("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 服务器[1,3](@ref)""" def run_gitea(): try: # 使用 subprocess.Popen 启动 Gitea 服务[1,9](@ref) gitea_process = subprocess.Popen( ['gitea', 'web', '--port', str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) logger.info(f"🚀 Gitea 服务器已启动,端口: {port}") logger.info(f"📊 访问地址: http://localhost:{port}") # 实时输出 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}") logger.error(f"错误信息: {error_output}") else: logger.info("✅ Gitea 服务器正常退出") except FileNotFoundError: logger.error("❌ 未找到 gitea 命令,请确保 Gitea 已正确安装") except Exception as e: logger.error(f"❌ 启动 Gitea 服务器时发生错误: {e}") # 在新线程中运行 Gitea[2,6](@ref) gitea_thread = threading.Thread(target=run_gitea) gitea_thread.daemon = True # 设置为守护线程,主程序退出时自动结束 gitea_thread.start() return gitea_thread def main(): """主函数 - 同时启动目录监控和 Gitea 服务器""" # 配置参数 CONFIG = { "data_directory": "/data", "repo_id": "02engine/02gitea", "hf_token": os.getenv('HF_TOKEN'), "gitea_port": 7860 } # 验证必要参数 if not CONFIG["hf_token"]: logger.error("❌ 请设置 HF_TOKEN 环境变量") return logger.info("🚀 启动集成服务...") try: # 启动 Gitea 服务器[1,3](@ref) 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("调试: HF_TOKEN = %s", CONFIG["hf_token"]) logger.info("📁 目录监控: /data → Hugging Face Hub") logger.info("🌐 Gitea 服务: http://localhost:7860") logger.info("🛑 按 Ctrl+C 停止所有服务") # 主循环,保持程序运行 try: while True: time.sleep(1) except KeyboardInterrupt: logger.info("正在停止所有服务...") except Exception as e: logger.error(f"❌ 启动服务时发生错误: {e}") finally: # 清理资源 if 'observer' in locals(): observer.stop() observer.join() logger.info("所有服务已停止") if __name__ == "__main__": main() EOF # 运行 Gitea,显式指定 Spaces 默认端口 RUN cat /uploadhf.py CMD ["python3", "/uploadhf.py"]