kink-discovery / scripts /sync_hf_space_b2_catalog.py
Perplexed7675's picture
Sync from kink_cli (Docker Space)
58ef86c verified
Raw
History Blame Contribute Delete
6.31 kB
#!/usr/bin/env python3
"""Upload the slim bootstrap SQLite to B2 and push B2 credentials to a Hugging Face Space.
Reads ``[b2]`` and ``[huggingface].token`` from parser ``secrets.toml`` (same defaults as
other scripts). Uploads ``data/store_slim_b2.db`` by default (output of
``shrink_store_for_remote_bootstrap.py`` — full kink rows, ~280 MiB).
Then calls ``HfApi.add_space_secret`` for ``B2_*`` and ``add_space_variable`` for
``KINK_B2_OBJECT_KEY`` so the Space bootstraps via S3 **before** Hub (see
``backend/hf_bootstrap``).
Requires:
export HF_SPACE_REPO=owner/slug
Does not print secret values.
After success, rebuild the Space so the container picks up secrets::
python scripts/publish_hf_space.py --verify
"""
from __future__ import annotations
import argparse
import os
import sys
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from backend.b2_toml import load_b2_section, resolve_b2_config_path
from backend.repo_dotenv import load_repo_dotenv
DEFAULT_SECRETS = Path(os.environ.get("KINK_PARSER_SECRETS_TOML", Path.home() / "PycharmProjects/parser/secrets.toml"))
def _load_hf_token(path: Path) -> str:
t = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or "").strip()
if t:
return t
with path.open("rb") as f:
data = tomllib.load(f)
hf = (data.get("huggingface") or {}).get("token") or ""
if not str(hf).strip():
raise SystemExit(f"Missing [huggingface].token in {path}")
return str(hf).strip()
def main() -> int:
load_repo_dotenv()
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument(
"--secrets-toml",
type=Path,
default=DEFAULT_SECRETS,
help="Parser secrets with [huggingface].token (and [b2] if --b2-secrets-toml not set)",
)
ap.add_argument(
"--b2-secrets-toml",
type=Path,
default=None,
help="TOML with only [b2] (overrides env KINK_B2_SECRETS_TOML and [b2] from --secrets-toml)",
)
ap.add_argument(
"--local-path",
type=Path,
default=ROOT / "data" / "store_slim_b2.db",
help="SQLite file to upload (default: shrunk bootstrap DB)",
)
ap.add_argument(
"--key",
default="kink/catalog/store_slim_bootstrap.db",
help="S3 object key (Space default KINK_B2_OBJECT_KEY must match)",
)
ap.add_argument("--skip-upload", action="store_true", help="Only push HF secrets/variables")
args = ap.parse_args()
repo_id = (os.environ.get("HF_SPACE_REPO") or "").strip()
if not repo_id:
print("sync_hf_space_b2_catalog: set HF_SPACE_REPO=owner/slug", file=sys.stderr)
return 1
if not args.secrets_toml.is_file():
print(f"sync_hf_space_b2_catalog: secrets not found: {args.secrets_toml}", file=sys.stderr)
return 1
b2_path = resolve_b2_config_path(cli_b2=args.b2_secrets_toml, cli_fallback=args.secrets_toml)
if not b2_path.is_file():
print(f"sync_hf_space_b2_catalog: B2 secrets TOML not found: {b2_path}", file=sys.stderr)
return 1
try:
b2 = load_b2_section(b2_path)
except SystemExit as e:
print(f"sync_hf_space_b2_catalog: {e}", file=sys.stderr)
return 1
try:
hf_token = _load_hf_token(args.secrets_toml)
except SystemExit as e:
print(f"sync_hf_space_b2_catalog: {e}", file=sys.stderr)
return 1
local = args.local_path.resolve()
if not args.skip_upload:
if not local.is_file():
print(f"sync_hf_space_b2_catalog: missing {local} — run shrink_store_for_remote_bootstrap.py first", file=sys.stderr)
return 1
import boto3
from huggingface_hub import HfApi
region = b2["region"]
endpoint = f"https://s3.{region}.backblazeb2.com"
client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=b2["key_id"],
aws_secret_access_key=b2["application_key"],
region_name=region,
)
env_bucket = (os.environ.get("KINK_B2_CATALOG_BUCKET") or os.environ.get("B2_CATALOG_BUCKET") or "").strip()
bucket = env_bucket or b2["bucket_name"]
key = args.key.lstrip("/")
if env_bucket:
print(f"sync_hf_space_b2_catalog: using bucket from env: {bucket}", file=sys.stderr)
if not args.skip_upload:
print(
f"sync_hf_space_b2_catalog: uploading {local} ({local.stat().st_size / 1e6:.1f} MB) → s3://{bucket}/{key}",
file=sys.stderr,
)
try:
client.upload_file(str(local), bucket, key, ExtraArgs={"ContentType": "application/x-sqlite3"})
except Exception as e:
err = str(e).lower()
if "storage cap" in err or "cap exceeded" in err:
print(
"sync_hf_space_b2_catalog: B2 **account** Caps & Alerts (storage $/day) hit — "
"not fixed by a new object key or new empty bucket on the same account. "
"Raise/remove caps in the B2 console, or use another account / KINK_B2_CATALOG_BUCKET on an account with room.",
file=sys.stderr,
)
raise
api = HfApi(token=hf_token)
def sec(name: str, value: str, desc: str) -> None:
api.add_space_secret(repo_id, name, value, description=desc, token=hf_token)
sec("B2_KEY_ID", b2["key_id"], "Backblaze B2 application key id (S3)")
sec("B2_APPLICATION_KEY", b2["application_key"], "Backblaze B2 application key secret")
sec("B2_BUCKET", bucket, "Backblaze B2 bucket for catalog object")
sec("B2_REGION", region, "Backblaze B2 region (e.g. us-east-005)")
api.add_space_variable(
repo_id,
"KINK_B2_OBJECT_KEY",
key,
description="S3 object key for store SQLite (bootstrap)",
token=hf_token,
)
print("sync_hf_space_b2_catalog: Space secrets + KINK_B2_OBJECT_KEY updated.", file=sys.stderr)
print("sync_hf_space_b2_catalog: Rebuild the Space:", file=sys.stderr)
print(f" HF_TOKEN=*** HF_SPACE_REPO={repo_id} python scripts/publish_hf_space.py --verify", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())