9router / Dockerfile
jualhosting's picture
Update Dockerfile
cb59c7c verified
Raw
History Blame Contribute Delete
3.63 kB
FROM node:20-slim
ENV NODE_ENV=production \
PORT=7860 \
HOSTNAME=0.0.0.0 \
NEXT_TELEMETRY_DISABLED=1
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip curl procps build-essential ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --break-system-packages --no-cache-dir huggingface_hub
RUN npm install -g 9router@0.4.52 --prefer-online --no-audit --no-fund \
&& mkdir -p /root/.9router/runtime \
&& cd /root/.9router/runtime \
&& npm install sql.js@1.13.0 better-sqlite3@12.6.2 --no-audit --no-fund --prefer-online
WORKDIR /app
RUN cat > /app/sync.py <<'PY'
import os
import tarfile
import time
import sys
from pathlib import Path
from huggingface_hub import HfApi, hf_hub_download
REPO_ID = os.environ.get("DATASET_REPO")
TOKEN = os.environ.get("HF_TOKEN")
BACKUP_FILE = "/tmp/9router_backup.tar.gz"
DATA_DIR = Path("/root/.9router")
def safe_extract(tar, path):
base = Path(path).resolve()
for member in tar.getmembers():
target = (base / member.name).resolve()
if not str(target).startswith(str(base)):
raise RuntimeError(f"unsafe tar member: {member.name}")
tar.extractall(path)
def upload():
if not REPO_ID or not TOKEN:
print(">>> [Sync] DATASET_REPO/HF_TOKEN 未配置,跳过备份")
return
if not DATA_DIR.exists():
print(">>> [Sync] 数据目录不存在,跳过备份")
return
try:
with tarfile.open(BACKUP_FILE, "w:gz") as tar:
tar.add(str(DATA_DIR), arcname=".9router")
HfApi(token=TOKEN).upload_file(
path_or_fileobj=BACKUP_FILE,
path_in_repo="9router_backup.tar.gz",
repo_id=REPO_ID,
repo_type="dataset",
)
print(">>> [Sync] 备份成功: " + time.strftime("%Y-%m-%d %H:%M:%S"), flush=True)
except Exception as e:
print(">>> [Sync] 备份失败: " + str(e), flush=True)
def download():
if not REPO_ID or not TOKEN:
print(">>> [Sync] DATASET_REPO/HF_TOKEN 未配置,跳过恢复")
return
try:
print(">>> [Sync] 正在从 Dataset 下载数据...", flush=True)
path = hf_hub_download(
repo_id=REPO_ID,
filename="9router_backup.tar.gz",
repo_type="dataset",
token=TOKEN,
)
with tarfile.open(path, "r:gz") as tar:
safe_extract(tar, "/root")
print(">>> [Sync] 数据恢复成功", flush=True)
except Exception as e:
print(">>> [Sync] 跳过恢复: " + str(e), flush=True)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "upload":
upload()
elif len(sys.argv) > 1 and sys.argv[1] == "download":
download()
PY
RUN cat > /app/run.sh <<'SH'
#!/bin/bash
set -euo pipefail
mkdir -p /root/.9router/runtime
echo '=== 1. 数据恢复 ==='
python3 /app/sync.py download
echo '=== 2. 启动定时备份 (每10分钟一次) ==='
(while true; do sleep 600; python3 /app/sync.py upload; done) &
echo '=== 3. 启动 9router on 7860 ==='
export PORT=7860
export HOSTNAME=0.0.0.0
export NEXT_PUBLIC_BASE_URL="https://${SPACE_HOST:-jualhosting-9router.hf.space}"
# 关键修复:不能用 `printf '\n' | 9router`。
# 9router 在 stdin 关闭后会触发 SIGINT/退出菜单,HF 看到 Exit code 0 后判定 RUNTIME_ERROR。
# --tray + --skip-update 会跳过交互菜单,并让 Node 主进程常驻。
exec 9router --tray --skip-update --no-browser --log --port 7860 --host 0.0.0.0
SH
RUN chmod +x /app/run.sh
EXPOSE 7860
CMD ["/bin/bash", "/app/run.sh"]