Spaces:
Sleeping
Sleeping
| """One-shot Hugging Face Space deploy for WitnessBox. | |
| Run AFTER an HF write token is available, either as: | |
| HF_TOKEN=hf_xxx python3 scripts/deploy_space.py | |
| or after `hf auth login` (the CLI stores the token; this script picks it up). | |
| What it does, idempotently: | |
| 1. Resolve the target namespace (personal by default; set WITNESSBOX_HF_ORG to | |
| push into an org you belong to, e.g. build-small-hackathon). | |
| 2. Create the Space (gradio SDK) if it doesn't exist. | |
| 3. Upload the app: app.py, config.py, modal_app.py, requirements.txt, README.md, | |
| and the witnessbox/ package (skips caches, tests, the local Modal token). | |
| 4. Set Space secrets so the live app talks to the deployed Modal app: | |
| MODAL_TOKEN_ID, MODAL_TOKEN_SECRET (read from ~/.modal.toml) | |
| WITNESSBOX_BACKEND=modal (as a public variable) | |
| 5. Print the Space URL. | |
| Nothing here is destructive; re-running just re-uploads + re-sets. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import re | |
| import sys | |
| REPO_NAME = os.environ.get("WITNESSBOX_SPACE_NAME", "WitnessBox") | |
| ORG = os.environ.get("WITNESSBOX_HF_ORG", "").strip() # empty => personal namespace | |
| ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| def _token() -> str: | |
| tok = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or "").strip() | |
| if tok: | |
| return tok | |
| # Fall back to a CLI-stored token (`hf auth login`). | |
| try: | |
| from huggingface_hub import HfFolder | |
| tok = HfFolder.get_token() or "" | |
| except Exception: | |
| tok = "" | |
| if not tok: | |
| sys.exit("No HF token. Set HF_TOKEN=hf_xxx (write scope) or run `hf auth login` first.") | |
| return tok | |
| def _modal_tokens() -> tuple[str, str]: | |
| """Pull token_id/token_secret out of ~/.modal.toml (no tomllib on py3.9).""" | |
| path = os.path.expanduser("~/.modal.toml") | |
| if not os.path.exists(path): | |
| return "", "" | |
| text = open(path).read() | |
| tid = re.search(r'token_id\s*=\s*"([^"]+)"', text) | |
| tsec = re.search(r'token_secret\s*=\s*"([^"]+)"', text) | |
| return (tid.group(1) if tid else ""), (tsec.group(1) if tsec else "") | |
| def main() -> int: | |
| from huggingface_hub import HfApi | |
| token = _token() | |
| api = HfApi(token=token) | |
| me = api.whoami() | |
| user = me["name"] | |
| namespace = ORG or user | |
| repo_id = f"{namespace}/{REPO_NAME}" | |
| print(f"HF user: {user} -> target Space: {repo_id}") | |
| # 1) Create the Space (gradio). exist_ok keeps this idempotent. | |
| api.create_repo(repo_id=repo_id, repo_type="space", space_sdk="gradio", | |
| exist_ok=True, token=token) | |
| print(f" space ready: https://huggingface.co/spaces/{repo_id}") | |
| # 2) Upload the app (whole repo minus junk; nothing here holds secrets — the | |
| # Modal token lives in ~/.modal.toml, outside the repo). fnmatch '*' spans | |
| # '/', so these substring globs catch nested caches too. | |
| ignore = ["*.pyc", "*__pycache__*", "*.pytest_cache*", "*.git*", | |
| "*.wav", "*.toml"] | |
| api.upload_folder( | |
| repo_id=repo_id, repo_type="space", folder_path=ROOT, | |
| ignore_patterns=ignore, token=token, | |
| commit_message="Deploy WitnessBox", | |
| ) | |
| print(" files uploaded") | |
| # 3) Wire the live backend: Modal secrets + backend switch. | |
| tid, tsec = _modal_tokens() | |
| if tid and tsec: | |
| api.add_space_secret(repo_id, "MODAL_TOKEN_ID", tid, token=token) | |
| api.add_space_secret(repo_id, "MODAL_TOKEN_SECRET", tsec, token=token) | |
| api.add_space_variable(repo_id, "WITNESSBOX_BACKEND", "modal", token=token) | |
| print(" secrets set: MODAL_TOKEN_ID / MODAL_TOKEN_SECRET; WITNESSBOX_BACKEND=modal") | |
| else: | |
| print(" WARNING: ~/.modal.toml not found/parsed — Space will boot in MOCK mode.") | |
| print(" Set MODAL_TOKEN_ID / MODAL_TOKEN_SECRET in the Space settings to go live.") | |
| print(f"\nDONE. Space: https://huggingface.co/spaces/{repo_id}") | |
| print("It will build, then run app.py. First live turn warms the Modal containers.") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |