#!/bin/sh set -eu PYTHON=/opt/venv/bin/python3 DATA_DIR="${DATA_DIR:-/app/data}" CONFIG="$DATA_DIR/config.toml" echo "[start] using python: $PYTHON" echo "[start] data dir: $DATA_DIR" # 1. 从 HF Bucket 恢复 data/ $PYTHON - <<'PYEOF' import os import sys from huggingface_hub import HfFileSystem token = os.environ.get("HF_TOKEN", "") repo = os.environ.get("HF_BUCKET_REPO", "") local_data = os.environ.get("DATA_DIR", "/app/data") def to_relative_path(remote_file: str, repo_name: str) -> str: prefixes = ( f"hf://buckets/{repo_name}/", f"buckets/{repo_name}/", f"/{repo_name}/", f"{repo_name}/", ) rel_path = remote_file while True: for prefix in prefixes: if rel_path.startswith(prefix): rel_path = rel_path[len(prefix):] break else: break return rel_path.lstrip("/") if not token or not repo: print("[bucket_pull] skipped: HF_TOKEN or HF_BUCKET_REPO not set") sys.exit(0) fs = HfFileSystem(token=token) bucket_root = f"hf://buckets/{repo}" os.makedirs(local_data, exist_ok=True) try: if not fs.exists(bucket_root): print(f"[bucket_pull] bucket {bucket_root} not found, skipping") sys.exit(0) files = fs.glob(f"{bucket_root}/**/*") pulled = 0 for remote_file in files: if fs.isfile(remote_file): rel_path = to_relative_path(remote_file, repo) if not rel_path or rel_path.startswith("buckets/"): print(f"[bucket_pull] skipped invalid path: {remote_file}") continue local_file = os.path.join(local_data, rel_path) os.makedirs(os.path.dirname(local_file), exist_ok=True) fs.get(remote_file, local_file) print(f"[bucket_pull] restored: {rel_path}") pulled += 1 print(f"[bucket_pull] done, {pulled} file(s) restored.") except Exception as exc: print(f"[bucket_pull] error: {exc}") PYEOF # 2. 初始化存储 if [ -f /app/scripts/init_storage.sh ]; then /app/scripts/init_storage.sh fi # 3. 注入 Secrets 到 config.toml if [ -n "${APP_KEY:-}" ] && [ -f "$CONFIG" ]; then sed -i "s|^app_key = .*|app_key = \"$APP_KEY\"|" "$CONFIG" fi if [ -n "${API_KEY:-}" ] && [ -f "$CONFIG" ]; then sed -i "s|^api_key = .*|api_key = \"$API_KEY\"|" "$CONFIG" fi export SERVER_STORAGE_TYPE="${SERVER_STORAGE_TYPE:-local}" export SERVER_STORAGE_URL="${SERVER_STORAGE_URL:-}" # 4. 后台:文件监听(防抖推送)+ 兜底定时同步 $PYTHON - <<'PYEOF' & import glob import os import threading import time from huggingface_hub import HfFileSystem from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer token = os.environ.get("HF_TOKEN", "") repo = os.environ.get("HF_BUCKET_REPO", "") data_dir = os.environ.get("DATA_DIR", "/app/data") bucket_root = f"hf://buckets/{repo}" def should_skip_local_path(rel_path: str) -> bool: rel_path = rel_path.replace("\\", "/") return not rel_path or rel_path.startswith("buckets/") def push(): if not token or not repo: return fs = HfFileSystem(token=token) files = [ path for path in glob.glob(os.path.join(data_dir, "**", "*"), recursive=True) if os.path.isfile(path) ] if not files: return try: for local_path in files: rel_path = os.path.relpath(local_path, data_dir).replace("\\", "/") if should_skip_local_path(rel_path): continue remote_path = f"{bucket_root}/{rel_path}" fs.put(local_path, remote_path) print(f"[bucket_push] pushed {len(files)} file(s)", flush=True) except Exception as exc: print(f"[bucket_push] error: {exc}", flush=True) class Handler(FileSystemEventHandler): def __init__(self): self._timer = None self._lock = threading.Lock() def _schedule(self): with self._lock: if self._timer: self._timer.cancel() self._timer = threading.Timer(5, push) self._timer.start() def on_modified(self, event): if not event.is_directory: self._schedule() def on_created(self, event): if not event.is_directory: self._schedule() def on_deleted(self, event): if not event.is_directory: self._schedule() def on_moved(self, event): if not event.is_directory: self._schedule() if token and repo: os.makedirs(data_dir, exist_ok=True) observer = Observer() observer.schedule(Handler(), path=data_dir, recursive=True) observer.start() print(f"[bucket_watch] watching {data_dir}...", flush=True) def periodic(): while True: time.sleep(300) push() threading.Thread(target=periodic, daemon=True).start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() else: print("[bucket_watch] skipped: HF_TOKEN or HF_BUCKET_REPO not set", flush=True) while True: time.sleep(3600) PYEOF # 5. 启动应用 echo "[start] starting grok2api..." exec /opt/venv/bin/granian --interface asgi \ --host "${SERVER_HOST:-0.0.0.0}" \ --port "${SERVER_PORT:-7860}" \ --workers "${SERVER_WORKERS:-1}" \ main:app