|
|
|
|
|
""" |
|
|
Utility to push Hugging Face Space secrets from a local env file. |
|
|
|
|
|
Usage: |
|
|
export HF_TOKEN=hf_xxx # token with write access to the Space |
|
|
python backend/scripts/update_hf_space_secrets.py \ |
|
|
--space davidttran999/hue-portal-backendDocker \ |
|
|
--secrets-file ops/hf.secrets.env |
|
|
""" |
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
import argparse |
|
|
import os |
|
|
import sys |
|
|
from pathlib import Path |
|
|
from typing import Dict |
|
|
|
|
|
import requests |
|
|
|
|
|
|
|
|
def parse_env_file(path: Path) -> Dict[str, str]: |
|
|
""" |
|
|
Load KEY=VALUE pairs from the provided file. |
|
|
|
|
|
Blank lines and comments starting with `#` are ignored. |
|
|
""" |
|
|
if not path.exists(): |
|
|
raise FileNotFoundError(f"Secrets file not found: {path}") |
|
|
|
|
|
secrets: Dict[str, str] = {} |
|
|
for raw_line in path.read_text(encoding="utf-8").splitlines(): |
|
|
line = raw_line.strip() |
|
|
if not line or line.startswith("#"): |
|
|
continue |
|
|
if "=" not in line: |
|
|
raise ValueError(f"Invalid secret line (missing '='): {raw_line}") |
|
|
key, value = line.split("=", 1) |
|
|
secrets[key.strip()] = value.strip() |
|
|
|
|
|
if not secrets: |
|
|
raise ValueError(f"No secrets detected in {path}") |
|
|
return secrets |
|
|
|
|
|
|
|
|
def upsert_secret(space_id: str, token: str, key: str, value: str) -> None: |
|
|
"""Create or update a secret for the given Hugging Face Space.""" |
|
|
url = f"https://huggingface.co/api/spaces/{space_id}/secrets" |
|
|
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} |
|
|
response = requests.post(url, headers=headers, json={"key": key, "value": value}, timeout=30) |
|
|
if response.status_code != 200: |
|
|
raise RuntimeError( |
|
|
f"Failed to upsert secret '{key}'. " |
|
|
f"Status {response.status_code}: {response.text}" |
|
|
) |
|
|
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser: |
|
|
"""Configure CLI options.""" |
|
|
parser = argparse.ArgumentParser(description="Sync secrets to a Hugging Face Space.") |
|
|
parser.add_argument( |
|
|
"--space", |
|
|
required=True, |
|
|
help="Space identifier in the form owner/space (e.g. davidttran999/hue-portal-backendDocker).", |
|
|
) |
|
|
parser.add_argument( |
|
|
"--secrets-file", |
|
|
default="ops/hf.secrets.env", |
|
|
help="Path to file containing KEY=VALUE entries (default: %(default)s).", |
|
|
) |
|
|
parser.add_argument( |
|
|
"--token-env", |
|
|
default="HF_TOKEN", |
|
|
help="Environment variable that stores the Hugging Face access token (default: %(default)s).", |
|
|
) |
|
|
return parser |
|
|
|
|
|
|
|
|
def main() -> None: |
|
|
"""CLI entry point.""" |
|
|
parser = build_parser() |
|
|
args = parser.parse_args() |
|
|
|
|
|
token = os.environ.get(args.token_env) |
|
|
if not token: |
|
|
parser.error(f"Environment variable {args.token_env} is not set.") |
|
|
|
|
|
secrets = parse_env_file(Path(args.secrets_file).expanduser()) |
|
|
for key, value in secrets.items(): |
|
|
upsert_secret(args.space, token, key, value) |
|
|
print(f"✅ Synced secret '{key}' to {args.space}") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
try: |
|
|
main() |
|
|
except Exception as exc: |
|
|
print(f"❌ {exc}", file=sys.stderr) |
|
|
sys.exit(1) |
|
|
|
|
|
|