File size: 7,231 Bytes
c6302b1 a009492 c6302b1 a009492 c6302b1 a009492 feec173 c6302b1 feec173 c6302b1 feec173 c6302b1 feec173 a009492 feec173 c6302b1 feec173 c6302b1 feec173 a009492 feec173 c6302b1 a009492 feec173 a009492 feec173 c6302b1 feec173 a009492 feec173 c6302b1 feec173 c6302b1 feec173 a009492 feec173 c6302b1 feec173 c6302b1 a009492 c6302b1 feec173 a009492 c6302b1 | 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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | #!/usr/bin/env bash
# Deploy this repo to a Hugging Face Docker Space.
#
# Requires an authenticated Hugging Face CLI session that can create Spaces and
# Buckets. XTAP_STORAGE_TOKEN must have read/write access to exactly the raw and
# index Buckets. Supplying it authorizes this script to install it as the Space
# HF_TOKEN secret.
#
# Usage:
# PINNED_IMPORT_REPORT=/safe/path/report.json XTAP_STORAGE_TOKEN=... \
# scripts/deploy-space.sh <namespace>
# SPACE_REPO=<ns>/<name> RAW_BUCKET=<ns>/<name> INDEX_BUCKET=<ns>/<name> \
# PINNED_IMPORT_REPORT=/safe/path/report.json XTAP_STORAGE_TOKEN=... \
# scripts/deploy-space.sh
# Set ALLOW_EMPTY_POOL=1 only for a confirmed new pool with no legacy data.
set -euo pipefail
NAMESPACE="${1:-${NAMESPACE:-}}"
SPACE_REPO="${SPACE_REPO:-${NAMESPACE:?usage: deploy-space.sh <namespace>}/xtap-pool}"
RAW_BUCKET="${RAW_BUCKET:-${NAMESPACE}/xtap-pool-data}"
INDEX_BUCKET="${INDEX_BUCKET:-${NAMESPACE}/xtap-pool-bucket}"
ALLOWED_USERS="${ALLOWED_USERS:-osolmaz}"
LLM_MODEL="${LLM_MODEL:-zai-org/GLM-5.2:fireworks-ai}"
TAXONOMY_VERSION="${TAXONOMY_VERSION:-1}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
STAGE="$(mktemp -d)"
INDEX_WORK="$(mktemp -d)"
trap 'rm -rf "$STAGE" "$INDEX_WORK"' EXIT
echo "==> Creating storage and Space resources (idempotent)"
hf buckets create "$RAW_BUCKET" --private --exist-ok
hf buckets create "$INDEX_BUCKET" --private --exist-ok
# The Space itself is public: a private Space would put HF's repo-access gate
# in front of the app. Anonymous visitors still see only the sign-in page.
hf repos create "$SPACE_REPO" --repo-type space --space-sdk docker 2>/dev/null || true
if [[ -z "${XTAP_STORAGE_TOKEN:-}" ]]; then
cat >&2 <<EOF
XTAP_STORAGE_TOKEN is required before deployment.
Create a fine-grained token with read/write access to exactly:
Bucket: $RAW_BUCKET
Bucket: $INDEX_BUCKET
Rerun with XTAP_STORAGE_TOKEN set. The script will bootstrap the durable index
and install the same value as the Space HF_TOKEN.
EOF
exit 2
fi
if [[ "${ALLOW_EMPTY_POOL:-0}" != "1" ]]; then
if [[ -z "${PINNED_IMPORT_REPORT:-}" || ! -f "$PINNED_IMPORT_REPORT" ]]; then
cat >&2 <<EOF
PINNED_IMPORT_REPORT must name the verified report from storage:import.
The deployment will not bootstrap an empty replacement over a legacy pool.
For a confirmed new pool with no legacy data, set ALLOW_EMPTY_POOL=1 explicitly.
EOF
exit 2
fi
SOURCE_PROOF="$(SPACE_REPO="$SPACE_REPO" RAW_BUCKET="$RAW_BUCKET" \
PINNED_IMPORT_REPORT="$PINNED_IMPORT_REPORT" python3 <<'PY'
import json
import os
import re
from huggingface_hub import HfApi
with open(os.environ["PINNED_IMPORT_REPORT"], encoding="utf-8") as handle:
report = json.load(handle)
if report.get("reconciliation", {}).get("passed") is not True:
raise SystemExit("pinned import report did not pass reconciliation")
target = report.get("target", {})
if target.get("bucket") != os.environ["RAW_BUCKET"]:
raise SystemExit("pinned import report targets a different raw Bucket")
if not isinstance(target.get("objects"), int) or target["objects"] < 1:
raise SystemExit("pinned import report has no imported objects")
if re.fullmatch(r"[a-f0-9]{64}", str(target.get("snapshot_revision", ""))) is None:
raise SystemExit("pinned import report has no valid snapshot revision")
source = report.get("source", {})
source_repo = str(source.get("dataset", ""))
source_revision = str(source.get("revision", ""))
if re.fullmatch(r"[a-f0-9]{40}", source_revision) is None:
raise SystemExit("pinned import report has no valid dataset revision")
api = HfApi()
variables = dict(api.get_space_variables(os.environ["SPACE_REPO"]))
if "DATASET_REPO" in variables:
raise SystemExit(
"legacy writers are not cut over: pause the Space, suspend enrichment, "
"pin and import the final dataset revision, then remove DATASET_REPO in the "
"coordinated cutover before using this deployment script"
)
if api.dataset_info(source_repo).sha != source_revision:
raise SystemExit("dataset advanced after the pinned import; quiesce writers and re-import")
print(source_repo)
print(source_revision)
PY
)"
SOURCE_DATASET="$(head -n 1 <<<"$SOURCE_PROOF")"
SOURCE_REVISION="$(tail -n 1 <<<"$SOURCE_PROOF")"
echo "==> Re-verifying the pinned source against every current raw Bucket object"
VERIFY_REPORT="$INDEX_WORK/verified-report.json"
HF_TOKEN="$XTAP_STORAGE_TOKEN" npm --prefix "$ROOT" run storage:verify -- \
--dataset "$SOURCE_DATASET" \
--revision "$SOURCE_REVISION" \
--raw-bucket "$RAW_BUCKET" \
--report "$VERIFY_REPORT" \
--work-dir "$INDEX_WORK/verify"
else
echo "==> Initializing canonical raw configuration for the confirmed new pool"
DATA_DIR="$INDEX_WORK" \
RAW_BUCKET="$RAW_BUCKET" \
HF_TOKEN="$XTAP_STORAGE_TOKEN" \
ALLOWED_USERS="$ALLOWED_USERS" \
POOL_ADMINS="${POOL_ADMINS:-$ALLOWED_USERS}" \
npm --prefix "$ROOT" run storage:initialize
fi
echo "==> Bootstrapping and verifying the durable index"
DATA_DIR="$INDEX_WORK" \
RAW_BUCKET="$RAW_BUCKET" \
INDEX_BUCKET="$INDEX_BUCKET" \
HF_TOKEN="$XTAP_STORAGE_TOKEN" \
LLM_MODEL="$LLM_MODEL" \
TAXONOMY_VERSION="$TAXONOMY_VERSION" \
npm --prefix "$ROOT" run index:bootstrap
echo "==> Setting Space secrets and variables"
SPACE_REPO="$SPACE_REPO" \
RAW_BUCKET="$RAW_BUCKET" \
INDEX_BUCKET="$INDEX_BUCKET" \
ALLOWED_USERS="$ALLOWED_USERS" \
LLM_MODEL="$LLM_MODEL" \
TAXONOMY_VERSION="$TAXONOMY_VERSION" \
XTAP_STORAGE_TOKEN="$XTAP_STORAGE_TOKEN" \
python3 <<'PY'
import os
import secrets
from huggingface_hub import HfApi
space = os.environ["SPACE_REPO"]
api = HfApi()
variables = dict(api.get_space_variables(space))
for name in (
"RAW_BUCKET",
"INDEX_BUCKET",
"ALLOWED_USERS",
"LLM_MODEL",
"TAXONOMY_VERSION",
):
api.add_space_variable(space, name, os.environ[name])
api.add_space_secret(space, "HF_TOKEN", os.environ["XTAP_STORAGE_TOKEN"])
# Secrets cannot be listed back, so a sentinel variable marks that they were
# set once. Never rotate silently: rotating logs everyone out or disconnects
# every extension.
if "SECRETS_INITIALIZED" not in variables:
for name in ("POOL_SIGNING_SECRET", "SESSION_SECRET"):
api.add_space_secret(space, name, secrets.token_hex(32))
api.add_space_variable(space, "SECRETS_INITIALIZED", "1")
print("Set Bucket variables, contract variables, and the scoped HF_TOKEN.")
PY
echo "==> Staging Space contents"
git -C "$ROOT" archive HEAD | tar -x -C "$STAGE"
cp "$ROOT/space/hf-space-README.md" "$STAGE/README.md"
rm -rf "$STAGE/docs" "$STAGE/extension"
echo "==> Uploading to $SPACE_REPO"
hf upload "$SPACE_REPO" "$STAGE" . --repo-type space --commit-message "deploy: $(git -C "$ROOT" rev-parse --short HEAD)"
if [[ "${ALLOW_EMPTY_POOL:-0}" != "1" ]]; then
echo "==> Removing the retired dataset variable after replacement code is durable"
SPACE_REPO="$SPACE_REPO" python3 <<'PY'
import os
from huggingface_hub import HfApi
api = HfApi()
variables = dict(api.get_space_variables(os.environ["SPACE_REPO"]))
if "DATASET_REPO" in variables:
api.delete_space_variable(os.environ["SPACE_REPO"], key="DATASET_REPO")
PY
fi
echo "==> Done. Space: https://huggingface.co/spaces/$SPACE_REPO"
|