#!/usr/bin/env bash # Deploy Anti-Hallucination Chat to a Hugging Face Docker Space. # # Required: # HF_TOKEN Write-scoped token from https://huggingface.co/settings/tokens # GOOGLE_API_KEY Gemini API key (stored as a Space *secret*, never uploaded) # # Optional: # SPACE_ID e.g. your-user/anti-hallucination-chat # default: value in .hf-space-id # SPACE_PRIVATE set to 1 for a private Space (default: public) # SKIP_SECRET set to 1 to skip setting GOOGLE_API_KEY on the Space # # Usage (from repo root or this directory): # export HF_TOKEN=hf_... # export GOOGLE_API_KEY=... # bash scripts/deploy_hf_space.sh # # or: # SPACE_ID=myuser/anti-hallucination-chat bash scripts/deploy_hf_space.sh set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" TARGET_FILE="$ROOT/.hf-space-id" if [[ -z "${HF_TOKEN:-}" ]]; then echo "error: HF_TOKEN is not set" >&2 echo " Create a write token at https://huggingface.co/settings/tokens" >&2 echo " then: export HF_TOKEN=hf_..." >&2 exit 1 fi export HF_TOKEN export HUGGING_FACE_HUB_TOKEN="${HUGGING_FACE_HUB_TOKEN:-$HF_TOKEN}" HF=(uv run --with "huggingface_hub>=0.30" hf) echo "==> Checking Hugging Face auth" WHOAMI="$("${HF[@]}" auth whoami --format json 2>/dev/null || "${HF[@]}" auth whoami || true)" if [[ -z "$WHOAMI" || "$WHOAMI" == *"Not logged in"* || "$WHOAMI" == *"Error"* ]]; then echo "error: HF auth failed. Check HF_TOKEN (needs write scope)." >&2 exit 1 fi echo "$WHOAMI" if [[ -z "${SPACE_ID:-}" && -f "$TARGET_FILE" ]]; then SPACE_ID="$(tr -d '[:space:]' < "$TARGET_FILE")" fi if [[ -z "${SPACE_ID:-}" ]]; then USER_NAME="$( printf '%s\n' "$WHOAMI" | python3 -c ' import json, re, sys raw = sys.stdin.read().strip() try: data = json.loads(raw) print( data.get("name") or data.get("user") or data.get("fullname") or data.get("fullnameName") or "" ) except Exception: m = re.search(r"(?:name|user|fullname)[\"'\''\\s:]+([A-Za-z0-9_-]+)", raw, re.I) print(m.group(1) if m else "") ' )" if [[ -z "$USER_NAME" ]]; then echo "error: could not resolve HF username; set SPACE_ID=user/space-name" >&2 exit 1 fi SPACE_ID="${USER_NAME}/anti-hallucination-chat" fi if [[ ! "$SPACE_ID" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then echo "error: invalid SPACE_ID '$SPACE_ID' (expected namespace/name)" >&2 exit 1 fi echo "==> Target Space: $SPACE_ID" echo "==> Exporting requirements.txt" if command -v uv >/dev/null 2>&1; then uv export --no-hashes --no-dev -o requirements.txt else echo "warning: uv not found; using existing requirements.txt" >&2 fi VISIBILITY=(--public) if [[ "${SPACE_PRIVATE:-0}" == "1" ]]; then VISIBILITY=(--private) fi if "${HF[@]}" spaces info "$SPACE_ID" --format json >/dev/null 2>&1; then echo "==> Updating existing Docker Space" else echo "==> Creating Docker Space (exist-ok)" "${HF[@]}" repos create "$SPACE_ID" \ --type space \ --space-sdk docker \ "${VISIBILITY[@]}" \ --exist-ok fi echo "==> Uploading app (secrets and .env excluded)" "${HF[@]}" upload "$SPACE_ID" "$ROOT" . \ --type space \ --commit-message "Deploy Anti-Hallucination Chat" \ --exclude ".env" \ --exclude ".env.*" \ --exclude ".venv/**" \ --exclude "**/.venv/**" \ --exclude ".cursor/**" \ --exclude "**/.cursor/**" \ --exclude ".git/**" \ --exclude "**/__pycache__/**" \ --exclude "tests/**" \ --exclude "**/*.pyc" \ --exclude "*.tex" \ --exclude "plan.md" \ --exclude "benchmark_*.py" \ --exclude "uv.lock" \ --exclude ".python-version" if [[ "${SKIP_SECRET:-0}" != "1" ]]; then if [[ -z "${GOOGLE_API_KEY:-}" ]]; then echo "warning: GOOGLE_API_KEY not set — Space will build but chat will error until you add the secret:" >&2 echo " hf spaces secrets add $SPACE_ID --secrets GOOGLE_API_KEY=YOUR_KEY" >&2 else echo "==> Setting GOOGLE_API_KEY Space secret" "${HF[@]}" spaces secrets add "$SPACE_ID" \ --secrets "GOOGLE_API_KEY=${GOOGLE_API_KEY}" fi fi # Optional non-secret model overrides (visible in Space settings) if [[ -n "${CHAT_MODEL:-}" || -n "${AUDIT_MODEL:-}" || -n "${SEARCH_MODEL:-}" ]]; then echo "==> Setting optional model env vars on the Space" ENV_ARGS=() [[ -n "${CHAT_MODEL:-}" ]] && ENV_ARGS+=(--env "CHAT_MODEL=${CHAT_MODEL}") [[ -n "${AUDIT_MODEL:-}" ]] && ENV_ARGS+=(--env "AUDIT_MODEL=${AUDIT_MODEL}") [[ -n "${SEARCH_MODEL:-}" ]] && ENV_ARGS+=(--env "SEARCH_MODEL=${SEARCH_MODEL}") # Newer CLIs: hf spaces env / repos create --env; fall back to secrets-free note if "${HF[@]}" spaces --help 2>/dev/null | grep -q "env"; then "${HF[@]}" spaces env add "$SPACE_ID" "${ENV_ARGS[@]}" 2>/dev/null \ || echo "note: set CHAT_MODEL/AUDIT_MODEL/SEARCH_MODEL in Space Settings → Variables" >&2 else echo "note: set model overrides in Space Settings → Variables if desired" >&2 fi fi echo echo "Deployed: https://huggingface.co/spaces/${SPACE_ID}" echo "Build logs: https://huggingface.co/spaces/${SPACE_ID}/logs" echo echo "If the Space is asleep or still building, wait a minute then open the URL." echo "Free Docker Spaces may require HF Pro: https://huggingface.co/pro"