Spaces:
Paused
Paused
Download store.py from joddabod/bbuilder-host: direct link, hf CLI and curl.
- Browser
- Download file 3.4 kB
-
https://huggingface.co/spaces/joddabod/bbuilder-host/resolve/main/store.py
- Command line
-
hf download hf://spaces/joddabod/bbuilder-host/store.py
-
curl -L -o store.py https://huggingface.co/spaces/joddabod/bbuilder-host/resolve/main/store.py
3.4 kB
| """Durable storage for bots, backed by a private Hugging Face dataset. | |
| Space disks are ephemeral: a rebuild, a sleep/wake cycle or a maintenance restart wipes | |
| everything under /home/user. Without this module the host would silently lose every bot it | |
| was given. On boot we pull the dataset into DATA_DIR; on every deploy/start/stop we push the | |
| affected bot back up. | |
| The dataset is PRIVATE — it holds Nerimity bot tokens in bots/<id>/secret.json. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import shutil | |
| import threading | |
| import traceback | |
| from pathlib import Path | |
| from huggingface_hub import HfApi, snapshot_download | |
| DATA_DIR = Path(os.environ.get("BB_DATA_DIR", "/home/user/data")) | |
| BOTS_DIR = DATA_DIR / "bots" | |
| DATASET_ID = os.environ.get("BB_DATASET", "joddabod/bbuilder-store") | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| # Only these ever leave the container. Notably excludes node_modules. | |
| SYNC_FILES = ("bot.js", "bb-runtime.js", "package.json", "project.bbproj", "meta.json", "secret.json") | |
| _push_lock = threading.Lock() | |
| def available() -> bool: | |
| return bool(HF_TOKEN) | |
| def _api() -> HfApi: | |
| return HfApi(token=HF_TOKEN) | |
| def pull_all() -> str: | |
| """Restore every bot from the dataset into DATA_DIR. Returns a human-readable summary.""" | |
| if not available(): | |
| return "no HF_TOKEN — running without durable storage (bots will not survive restarts)" | |
| try: | |
| snap = snapshot_download( | |
| repo_id=DATASET_ID, | |
| repo_type="dataset", | |
| token=HF_TOKEN, | |
| allow_patterns=["bots/**"], | |
| ) | |
| except Exception as exc: # noqa: BLE001 | |
| return f"restore skipped: {exc}" | |
| src = Path(snap) / "bots" | |
| if not src.exists(): | |
| return "store is empty (no bots yet)" | |
| BOTS_DIR.mkdir(parents=True, exist_ok=True) | |
| restored = 0 | |
| for bot_dir in src.iterdir(): | |
| if not bot_dir.is_dir(): | |
| continue | |
| dest = BOTS_DIR / bot_dir.name | |
| dest.mkdir(parents=True, exist_ok=True) | |
| for name in SYNC_FILES: | |
| f = bot_dir / name | |
| if f.exists(): | |
| shutil.copy2(f, dest / name) | |
| if (dest / "secret.json").exists(): | |
| (dest / "secret.json").chmod(0o600) | |
| restored += 1 | |
| return f"restored {restored} bot(s) from {DATASET_ID}" | |
| def push_bot(bot_id: str) -> None: | |
| """Mirror one bot's directory up to the dataset. Best-effort, never fatal.""" | |
| if not available(): | |
| return | |
| src = BOTS_DIR / bot_id | |
| if not src.is_dir(): | |
| return | |
| with _push_lock: | |
| try: | |
| _api().upload_folder( | |
| repo_id=DATASET_ID, | |
| repo_type="dataset", | |
| folder_path=str(src), | |
| path_in_repo=f"bots/{bot_id}", | |
| allow_patterns=list(SYNC_FILES), | |
| commit_message=f"sync {bot_id}", | |
| ) | |
| except Exception: # noqa: BLE001 | |
| traceback.print_exc() | |
| def push_bot_async(bot_id: str) -> None: | |
| threading.Thread(target=push_bot, args=(bot_id,), daemon=True).start() | |
| def delete_bot(bot_id: str) -> None: | |
| if not available(): | |
| return | |
| try: | |
| _api().delete_folder( | |
| path_in_repo=f"bots/{bot_id}", | |
| repo_id=DATASET_ID, | |
| repo_type="dataset", | |
| commit_message=f"delete {bot_id}", | |
| ) | |
| except Exception: # noqa: BLE001 | |
| pass | |