vani-speech-api / deploy.sh
radhapreetam's picture
Upload folder using huggingface_hub
33ac5c6 verified
Raw History Blame Contribute Delete
6.56 kB
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# VANI Speech API β€” one-command deploy to Hugging Face Spaces.
#
# PURPOSE: create (or update) the Space, push this folder, wait for it to
# build, then PROVE it is live by exercising the real endpoints over https
# from outside β€” health, a real text-to-speech round trip, and a real
# speech-to-text round trip using the audio that text-to-speech just produced.
#
# WHEN TO RUN: from RP's own terminal, which is the only shell with a Hugging
# Face credential. Agent shells are not logged in (verified 2026-08-21: both
# `hf repos create` and `hf upload` return 401 Unauthorized).
#
# WHEN NOT TO RUN: not from an agent session, and not as a scheduled job.
# It is a deliberate, interactive deployment step.
#
# This script never asks for, prints, or stores a token. It only checks that
# a credential is already present.
# ---------------------------------------------------------------------------
set -euo pipefail
SPACE_ID="${VANI_SPACE_ID:-radhapreetam/vani-speech-api}"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SUB="$(echo "$SPACE_ID" | tr '/' '-' | tr '[:upper:]' '[:lower:]')"
BASE="https://${SUB}.hf.space"
say(){ printf '\n\033[1m== %s\033[0m\n' "$*"; }
die(){ printf '\n\033[31mFAILED: %s\033[0m\n' "$*" >&2; exit 1; }
# --- Gate 1: credential present (presence only, never the value) ------------
say "Gate 1/5 β€” checking you are logged in to Hugging Face"
if ! WHO="$(hf auth whoami 2>&1)"; then
die "Not logged in. Run 'hf auth login' yourself and paste your own token, then re-run this script. Never share the token."
fi
echo "Logged in as: $WHO"
# --- Gate 2: the files we are about to ship are the ones we tested ----------
say "Gate 2/5 β€” checking the package is complete"
for f in app.py requirements.txt SPACE_CARD.md packages.txt; do
[ -f "$HERE/$f" ] || die "missing $f"
done
# On a Space, README.md is not documentation β€” its YAML frontmatter IS the
# build configuration (sdk, sdk_version, app_file). The workspace automation
# scripts/folder_readme.py rewrites README.md in every folder and would strip
# that frontmatter, leaving a Space that cannot build. So README.md is
# regenerated here, at upload time, from SPACE_CARD.md β€” which that automation
# does not touch.
cp "$HERE/SPACE_CARD.md" "$HERE/README.md"
head -1 "$HERE/README.md" | grep -q '^---$' || die "SPACE_CARD.md has lost its YAML frontmatter β€” the Space will not build without it."
grep -q '^app_file: app.py$' "$HERE/README.md" || die "Space card is missing 'app_file: app.py'."
# Hugging Face rejects a Space whose short_description exceeds 60 characters, and it
# only says so at UPLOAD time - after the repo has already been created. That cost a
# real failed deploy on 2026-08-21 (61 characters, one over). Validate it HERE, before
# anything is created, so the failure is cheap and self-explaining rather than a
# mid-deploy error nobody can parse.
_desc="$(sed -n 's/^short_description:[[:space:]]*//p' README.md | head -1)"
_len=${#_desc}
if [ "$_len" -gt 60 ]; then
echo "STOP - short_description is ${_len} characters; Hugging Face allows 60."
echo " Shorten it in SPACE_CARD.md (README.md is regenerated from it), then re-run."
exit 1
fi
echo "Space card regenerated from SPACE_CARD.md; frontmatter verified (short_description ${_len}/60)."
grep -q "^mlx" "$HERE/requirements.txt" && die "requirements.txt contains an mlx package β€” those are Apple-silicon only and will not build on a Linux Space."
python3 -c "import ast,sys; ast.parse(open('$HERE/app.py').read())" || die "app.py does not parse"
echo "app.py parses; no Apple-silicon-only dependency present."
# --- Gate 3: create the Space if it does not exist yet ----------------------
say "Gate 3/5 β€” ensuring the Space exists"
if hf repos create "$SPACE_ID" --type space --sdk gradio 2>/dev/null; then
echo "Created $SPACE_ID"
else
echo "$SPACE_ID already exists (or creation was declined) β€” continuing to upload."
fi
# --- Gate 4: upload -------------------------------------------------------
say "Gate 4/5 β€” uploading the package"
hf upload "$SPACE_ID" "$HERE" . --repo-type space \
--exclude "__pycache__/*" --exclude "*.pyc" --exclude "DEPLOY.md" \
|| die "upload failed"
# --- Gate 5: prove it is actually live, from outside -----------------------
say "Gate 5/5 β€” proving it is live at $BASE (this includes a cold start; allow ~5 minutes)"
echo "Waiting for /health to answer over https..."
for i in $(seq 1 60); do
if curl -fsS -m 15 "$BASE/health" >/tmp/vani_health.json 2>/dev/null; then
echo "PROOF 1 β€” https health check answered from outside:"; cat /tmp/vani_health.json; echo; break
fi
printf '.'; sleep 15
[ "$i" -eq 60 ] && die "Space never answered /health. Open $BASE and read the build logs."
done
echo "Warming the models (first load downloads them)..."
curl -fsS -m 900 -X POST "$BASE/warmup"; echo
echo
echo "PROOF 2 β€” real text in, real audio out:"
curl -fsS -m 300 -X POST "$BASE/text-to-speech" \
-H 'Content-Type: application/json' \
-d '{"text":"Book the badminton court for Sunday morning."}' \
-o /tmp/vani_live.wav || die "text-to-speech failed"
file /tmp/vani_live.wav
echo
echo "PROOF 3 β€” real audio in, real text out (feeding back the audio just generated):"
curl -fsS -m 300 -X POST "$BASE/speech-to-text" \
-F "audio=@/tmp/vani_live.wav;type=audio/wav" || die "speech-to-text failed"
echo
echo
echo "PROOF 4 β€” the page a stranger can open:"
curl -fsS -m 30 -o /dev/null -w "GET %{url_effective} -> HTTP %{http_code}\n" "$BASE/"
cat <<EOF
===========================================================================
LIVE at: $BASE
demo page $BASE/
health $BASE/health
speech-to-text POST $BASE/speech-to-text (multipart, field "audio")
text-to-speech POST $BASE/text-to-speech (JSON {"text": "..."})
Open $BASE/ in a browser and walk it once yourself β€” press and hold the
button, say a sentence, and confirm the words appear. A browser is the only
place the microphone permission prompt can actually be exercised.
If the Space is PRIVATE, only you can open it. Make it public in
Settings -> Change visibility before sending the link to anyone else.
NEXT, SEPARATELY: the website page is prepared but NOT deployed. Review
"Working Files/Vani/website_wiring/vani-demo.hosted.patch"
and apply it to rp-website only when you want the live site to switch over.
===========================================================================
EOF