lx / Dockerfile
wd21's picture
Update Dockerfile
cfafa7b verified
# 使用 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"]