FROM decolua/9router:latest # 1. Switch to root to install system packages USER root # 2. Install Python for the backup script RUN apk add --no-cache python3 py3-pip tar \ && pip3 install --break-system-packages --no-cache-dir huggingface_hub WORKDIR /app # 3. Create the Python sync script 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("/app/data") 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 or HF_TOKEN not configured. Skipping backup.") return if not DATA_DIR.exists(): print(">>> [Sync] Data directory does not exist. Skipping backup.") return try: with tarfile.open(BACKUP_FILE, "w:gz") as tar: # We pack the /app/data folder as 'data' tar.add(str(DATA_DIR), arcname="data") 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(f">>> [Sync] Backup successful: {time.strftime('%Y-%m-%d %H:%M:%S')}", flush=True) except Exception as e: print(f">>> [Sync] Backup failed: {e}", flush=True) def download(): if not REPO_ID or not TOKEN: print(">>> [Sync] DATASET_REPO or HF_TOKEN not configured. Skipping restore.") return try: print(">>> [Sync] Downloading data from 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: # Extracting into /app will perfectly place it at /app/data safe_extract(tar, "/app") print(">>> [Sync] Data restore successful.", flush=True) except Exception as e: print(f">>> [Sync] Skipping restore (might be first run): {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 # 4. Create the startup shell script RUN cat > /app/run.sh <<'SH' #!/bin/sh set -e echo '=== 0. Resetting Local Data ===' mkdir -p /app/data echo '=== 1. Restoring Data ===' python3 /app/sync.py download echo '=== 2. Starting Scheduled Backup (Every 10 minutes) ===' (while true; do sleep 600; python3 /app/sync.py upload; done) & echo '=== 3. Starting 9router ===' export PORT=7860 export HOSTNAME=0.0.0.0 export DATA_DIR=/app/data export NEXT_PUBLIC_BASE_URL="https://${SPACE_HOST:-otnansirk-9router.hf.space}" echo '=== Checking 9router files ===' pwd ls -la node -e "console.log(require('./package.json').version)" || true cat package.json | grep version || true echo '=== 3. Starting 9router ===' exec node server.js SH RUN chmod +x /app/run.sh EXPOSE 7860 CMD ["/bin/sh", "/app/run.sh"]