# 使用 lxmusic 官方镜像(Alpine 基础) FROM xcq0607/lxserver:latest # 安装必要工具 RUN apk add --no-cache python3 py3-pip curl socat tar && \ pip3 install --no-cache-dir --break-system-packages requests huggingface_hub && \ rm -rf /var/cache/apk/* # ========= 备份恢复脚本 sync.py(修复 API 调用错误) ========= RUN cat > /usr/local/bin/sync.py << 'SYNC_EOF' #!/usr/bin/env python3 import os, sys, tarfile, tempfile, time from datetime import datetime, timedelta from huggingface_hub import HfApi from requests.exceptions import HTTPError LX_DATA_BASE = "/server" BACKUP_DIRS = ["data", "logs", "cache"] CONFIG_FILES = ["config.json"] BACKUP_PREFIX = "lxmusic_backup_" KEEP_DAYS = 7 RETRY_COUNT = 3 RETRY_DELAY = 5 def log(msg): print(f"[SYNC] {msg}") def get_env(var): return os.getenv(var, "").strip() def validate_hf_access(repo_id, token): api = HfApi() try: api.repo_info(repo_id=repo_id, repo_type="dataset", token=token) except HTTPError as e: if e.response and e.response.status_code == 404: log(f"仓库 {repo_id} 不存在,尝试创建...") api.create_repo(repo_id=repo_id, repo_type="dataset", token=token, private=True) log("仓库创建成功") else: raise PermissionError(f"访问仓库失败: {e}") # 测试写入权限(使用关键字参数) test_file = tempfile.NamedTemporaryFile(mode="w", delete=False) test_file.write("test") test_file.close() try: api.upload_file( path_or_fileobj=test_file.name, path_in_repo=".write_test", repo_id=repo_id, repo_type="dataset", token=token ) api.delete_file(path_in_repo=".write_test", repo_id=repo_id, repo_type="dataset", token=token) log("写入权限验证通过") except Exception as e: raise PermissionError(f"写入权限不足: {e}") finally: os.unlink(test_file.name) def restore(): repo_id = get_env("HF_DATASET") token = get_env("HF_TOKEN") if not repo_id or not token: log("未设置 HF_DATASET 或 HF_TOKEN,跳过恢复") return False try: validate_hf_access(repo_id, token) except Exception as e: log(f"访问验证失败: {e}") return False api = HfApi() try: files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token) except Exception as e: log(f"列出文件失败: {e}") return False candidates = [] for i in range(KEEP_DAYS): day = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d") name = f"{BACKUP_PREFIX}{day}.tar.gz" if name in files: candidates.append(name) if not candidates: log("未找到最近备份") return False latest = sorted(candidates, reverse=True)[0] log(f"找到备份: {latest},开始下载") for attempt in range(RETRY_COUNT): try: local = api.hf_hub_download( repo_id=repo_id, filename=latest, repo_type="dataset", token=token, resume=True ) break except Exception as e: log(f"下载失败 ({attempt+1}/{RETRY_COUNT}): {e}") time.sleep(RETRY_DELAY) else: return False try: with tarfile.open(local, "r:gz") as tar: tar.extractall(path=LX_DATA_BASE) log("备份恢复成功") return True except Exception as e: log(f"解压失败: {e}") return False def backup(): repo_id = get_env("HF_DATASET") token = get_env("HF_TOKEN") if not repo_id or not token: log("未设置备份环境变量,跳过备份") return try: validate_hf_access(repo_id, token) except Exception as e: log(f"备份验证失败: {e}") return day = datetime.now().strftime("%Y-%m-%d") backup_name = f"{BACKUP_PREFIX}{day}.tar.gz" with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as tmp: tmp.close() with tarfile.open(tmp.name, "w:gz") as tar: for d in BACKUP_DIRS: src = f"{LX_DATA_BASE}/{d}" if os.path.exists(src): tar.add(src, arcname=d) for cfg in CONFIG_FILES: src = f"{LX_DATA_BASE}/{cfg}" if os.path.exists(src): tar.add(src, arcname=cfg) api = HfApi() api.upload_file( path_or_fileobj=tmp.name, path_in_repo=backup_name, repo_id=repo_id, repo_type="dataset", token=token ) log(f"备份 {backup_name} 上传成功") # 清理旧备份 files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token) cutoff = datetime.now() - timedelta(days=KEEP_DAYS) for f in files: if f.startswith(BACKUP_PREFIX) and f.endswith(".tar.gz"): date_str = f[len(BACKUP_PREFIX):-7] try: if datetime.strptime(date_str, "%Y-%m-%d") < cutoff: api.delete_file(path_in_repo=f, repo_id=repo_id, repo_type="dataset", token=token) log(f"删除旧备份: {f}") except: pass os.unlink(tmp.name) if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "backup": backup() else: restore() SYNC_EOF RUN chmod +x /usr/local/bin/sync.py # ========= 启动脚本 ========= RUN printf '#!/bin/sh\n\ set -e\n\ \n\ echo "===== LXMusic Server with Backup/Restore ====="\n\ \n\ mkdir -p /server/data /server/logs /server/cache\n\ \n\ python3 /usr/local/bin/sync.py restore\n\ \n\ (\n\ while true; do\n\ sleep 1800\n\ echo "--- 定时备份 ---"\n\ python3 /usr/local/bin/sync.py backup\n\ done\n\ ) &\n\ echo "Background backup started (every 30 minutes)"\n\ \n\ cd /server\n\ exec node index.js\n\ ' > /start.sh && chmod +x /start.sh EXPOSE 9527 CMD ["/start.sh"]