File size: 5,113 Bytes
024efc3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Create and configure the Hugging Face Space in one step.

Reads the keys from `.env` and sets them as Space secrets over the API, so live credentials
never pass through a shell command line (and therefore never land in PSReadLine history).
Idempotent: safe to re-run to update settings on an existing Space.

    python scripts/setup_space.py --space DevSchretl/ubc-calendar-rag

Free Gradio Spaces must run on ZeroGPU hardware, which is the default here. This app requests
no GPU (there is no @spaces.GPU anywhere) so it consumes a ZeroGPU *hosting* slot but never any
GPU quota. Afterwards, push the code:

    git remote add hf https://huggingface.co/spaces/<space>
    git push hf main
"""

from __future__ import annotations

import argparse
import secrets
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from dotenv import dotenv_values  # noqa: E402

# Copied onto the Space as secrets. HF_TOKEN should be a READ-scoped token: the Space only
# ever pulls the private index dataset, so a write token there adds risk and no capability.
SECRET_KEYS = ("OPENAI_API_KEY", "COHERE_API_KEY", "ANTHROPIC_API_KEY", "HF_TOKEN")


def main() -> None:
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--space", required=True, help="e.g. DevSchretl/ubc-calendar-rag")
    p.add_argument("--env", type=Path, default=Path(".env"))
    p.add_argument("--hardware", default="zero-a10g",
                   help="ZeroGPU is what makes a free Gradio Space free (default: zero-a10g)")
    p.add_argument("--private", action="store_true", help="create it private; flip later in Settings")
    p.add_argument("--per-hour", type=int, default=30, help="DEMO_IP_PER_HOUR (default 30, good for testing)")
    p.add_argument("--per-day", type=int, default=60)
    p.add_argument("--daily-calls", type=int, default=400, help="global DEMO_DAILY_LLM_CALLS ceiling")
    p.add_argument("--salt", default="", help="reuse a specific DEMO_IP_SALT instead of generating one")
    args = p.parse_args()

    env = dotenv_values(args.env)
    missing = [k for k in SECRET_KEYS if not env.get(k)]
    if missing:
        sys.exit(f"{args.env} is missing: {', '.join(missing)}")

    index_repo = env.get("DEMO_INDEX_REPO")
    index_rev = env.get("DEMO_INDEX_REVISION")
    if not index_repo or not index_rev:
        sys.exit("Set DEMO_INDEX_REPO and DEMO_INDEX_REVISION in .env first "
                 "(run scripts/publish_index.py — it prints the sha to pin).")

    from huggingface_hub import HfApi

    api = HfApi()   # the CLI's logged-in WRITE token, for creating and configuring

    # Sanity-check the read token before baking it in, so a bad one fails here rather than as
    # a 401 buried in the Space's startup log.
    try:
        who = HfApi(token=env["HF_TOKEN"]).whoami()
        role = ((who.get("auth") or {}).get("accessToken") or {}).get("role")
        print(f"HF_TOKEN belongs to {who.get('name')} (role: {role})")
        if role not in ("read", "fineGrained"):
            print(f"  note: a '{role}' token is broader than this Space needs; a read token is enough.")
    except Exception as exc:
        sys.exit(f"The HF_TOKEN in {args.env} is not valid: {type(exc).__name__}: {exc}")

    # And that it can actually see the private index — the single most common startup failure.
    try:
        info = HfApi(token=env["HF_TOKEN"]).dataset_info(index_repo)
        if info.sha != index_rev:
            print(f"  warning: DEMO_INDEX_REVISION ({index_rev[:12]}…) is not the dataset HEAD "
                  f"({info.sha[:12]}…). That is fine if you pinned it deliberately.")
    except Exception as exc:
        sys.exit(f"HF_TOKEN cannot read {index_repo}: {type(exc).__name__}: {exc}")

    url = api.create_repo(
        args.space, repo_type="space", space_sdk="gradio",
        space_hardware=args.hardware, private=args.private, exist_ok=True,
    )
    print(f"Space ready: {url}")

    variables = {
        "DEMO_INDEX_REPO": index_repo,
        "DEMO_INDEX_REVISION": index_rev,
        "DEMO_DAILY_LLM_CALLS": str(args.daily_calls),
        "DEMO_IP_PER_HOUR": str(args.per_hour),
        "DEMO_IP_PER_DAY": str(args.per_day),
    }
    for key, value in variables.items():
        api.add_space_variable(args.space, key, value)
        print(f"  variable  {key} = {value}")

    salt = args.salt or env.get("DEMO_IP_SALT") or secrets.token_urlsafe(32)
    for key in SECRET_KEYS:
        api.add_space_secret(args.space, key, env[key])
        print(f"  secret    {key} = {env[key][:6]}… ({len(env[key])} chars)")
    api.add_space_secret(args.space, "DEMO_IP_SALT", salt)
    print(f"  secret    DEMO_IP_SALT = {salt[:6]}… (generated)" if not (args.salt or env.get("DEMO_IP_SALT"))
          else "  secret    DEMO_IP_SALT = (reused)")

    print(f"\nNow push the code:\n"
          f"  git remote add hf https://huggingface.co/spaces/{args.space}\n"
          f"  git push hf main\n"
          f"\nThen watch it start:\n"
          f"  hf spaces logs {args.space}")


if __name__ == "__main__":
    main()