#!/usr/bin/env bash # ============================================================ # OpenClaw HF Space 使用最新代码重建脚本 # # 用法: # ./rebuild-space.sh [HF_TOKEN] # # 示例: # ./rebuild-space.sh / hf_xxxxx # # 方式1:直接提供参数 # ./scripts/rebuild-space.sh / hf_xxxxx # # 方式2:设置环境变量 # export SPACE_REPO_ID="/" # export HF_TOKEN="hf_xxxxx" # ./scripts/rebuild-space.sh # # 方式3:使用缓存的 token(默认从 ~/.cache/huggingface/token 读取) # ./scripts/rebuild-space.sh / # # 环境变量 (可选): # SPACE_REPO_ID - Space 仓库 ID (例如 /) # HF_TOKEN - Hugging Face API Token # HF_TOKEN_FILE - Token 文件路径 (默认 ~/.cache/huggingface/token) # # 说明: # 此脚本用于将本地最新代码强制推送到 Hugging Face Space 并触发重建 # - 自动创建 Space(如果不存在) # - 覆盖 Space 中的所有文件 # - 排除 .git, logs, scripts, docs, tests 等非必要文件 # - 推送完成后自动请求 Space 重启以使更改生效 # # 注意事项: # 1. 确保 HF_TOKEN 有 write 权限 # 2. Space 必须使用 Docker SDK # 3. 推送后 Space 会自动重启(需等待构建完成) # 4. 此操作会覆盖 Space 中的现有文件 # ============================================================ # ---- Fix terminal for interactive input ---- if [[ -t 0 ]]; then stty sane 2>/dev/null || true fi update_readme_title() { local new_title="$1" local readme_file="$REPO_ROOT/README.md" local temp_file if [[ ! -f "$readme_file" ]]; then return 0 fi temp_file="$(mktemp)" if sed "s/^title:.*/title: $new_title/" "$readme_file" > "$temp_file"; then mv "$temp_file" "$readme_file" else rm -f "$temp_file" fi } restore_readme_title() { local original_title="$1" local readme_file="$REPO_ROOT/README.md" local temp_file if [[ ! -f "$readme_file" ]]; then return 0 fi temp_file="$(mktemp)" if sed "s/^title:.*/title: $original_title/" "$readme_file" > "$temp_file"; then mv "$temp_file" "$readme_file" else rm -f "$temp_file" fi } prompt_yes_no() { local prompt="$1" local default="${2:-}" local choice="" local answer="" while [[ -z "$answer" ]]; do if [[ -t 0 ]]; then if [[ -n "$default" ]]; then read -r -p "$prompt (Y/n) [$default]: " choice || true else read -r -p "$prompt (y/N) [$default]: " choice || true fi else choice="$default" fi choice="$(printf '%s' "$choice" | tr -d '\r\n' | tr '[:upper:]' '[:lower:]')" if [[ -z "$choice" ]]; then choice="$default" fi case "$choice" in y|yes) answer="yes" ;; n|no) answer="no" ;; *) printf 'Please answer y or n.\n' >&2 ;; esac done printf '%s' "$answer" } prompt_secret() { local prompt="$1" local value="" local char="" if [[ -t 0 && -t 1 ]]; then printf '%s: ' "$prompt" >&2 while IFS= read -r -s -n 1 char; do if [[ -z "$char" ]]; then break fi case "$char" in $'\e') break ;; '') break ;; *) value+="$char"; printf '*' >&2 ;; esac done printf '\n' >&2 fi printf '%s' "$value" } prompt_hf_account_switch() { local current_username="" local hf_whoami_output hf_whoami_output="$(hf auth whoami 2>&1 || true)" current_username="$(printf '%s\n' "$hf_whoami_output" | sed -nE 's/^[[:space:]]*user:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n 1)" if [[ -z "$current_username" ]]; then current_username="$(printf '%s\n' "$hf_whoami_output" | sed -nE 's/.*[Ll]ogged in as[[:space:]]+([^[:space:]]+).*/\1/p' | head -n 1)" fi if [[ -n "$current_username" ]] && [[ -t 0 ]]; then local use_current use_current="$(prompt_yes_no "HF CLI is logged in as '$current_username'. Use this user?" "y")" if [[ "$use_current" == "yes" ]]; then return 0 fi fi while true; do if [[ -t 0 ]]; then local token token="$(prompt_secret "Enter HF_TOKEN to switch account")" if [[ -z "$token" ]]; then printf '[WARN] Empty token, please try again or press Ctrl+C to cancel\n' >&2 continue fi local login_output login_output="$(hf auth login --token "$token" 2>&1)" if [[ $? -eq 0 ]]; then local verify_output verify_output="$(hf auth whoami 2>&1 || true)" local new_username new_username="$(printf '%s\n' "$verify_output" | sed -nE 's/^[[:space:]]*user:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n 1)" if [[ -z "$new_username" ]]; then new_username="$(printf '%s\n' "$verify_output" | sed -nE 's/.*[Ll]ogged in as[[:space:]]+([^[:space:]]+).*/\1/p' | head -n 1)" fi if [[ -n "$current_username" ]] && [[ -n "$new_username" ]] && [[ "$current_username" != "$new_username" ]]; then printf '[INFO] HF account switched: %s -> %s\n' "$current_username" "$new_username" elif [[ -n "$new_username" ]]; then printf '[INFO] HF login successful as: %s\n' "$new_username" else printf '[INFO] HF login successful\n' fi return 0 else printf '[ERROR] HF login failed: %s\n' "$login_output" >&2 continue fi else return 1 fi done } SPACE_REPO_ID="${1:-${SPACE_REPO_ID:-}}" HF_TOKEN="${2:-${HF_TOKEN:-}}" if [[ -t 0 ]]; then prompt_hf_account_switch || true fi if [[ -z "$SPACE_REPO_ID" ]]; then echo "Usage: $0 [HF_TOKEN]" echo "" echo "Examples:" echo " $0 / hf_xxxxx" echo " SPACE_REPO_ID=/ HF_TOKEN=hf_xxxxx $0" echo " $0 /" echo "" echo "Environment variables:" echo " SPACE_REPO_ID - Space repo ID (例如 /)" echo " HF_TOKEN - Hugging Face API Token" echo " HF_TOKEN_FILE - Token file path (default: ~/.cache/huggingface/token)" exit 1 fi if [[ -z "$HF_TOKEN" ]]; then HF_TOKEN_FILE="${HF_TOKEN_FILE:-$HOME/.cache/huggingface/token}" if [[ -f "$HF_TOKEN_FILE" ]]; then HF_TOKEN="$(cat "$HF_TOKEN_FILE")" fi fi if [[ -z "$HF_TOKEN" ]]; then echo "Error: HF_TOKEN is required. Provide as 2nd arg, set HF_TOKEN env var, or ensure ~/.cache/huggingface/token exists." exit 1 fi SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)" SPACE_NAME="${SPACE_REPO_ID##*/}" echo "============================================" echo "OpenClaw HF Space Rebuild Script" echo "============================================" echo "Space: $SPACE_REPO_ID" echo "Repo: $REPO_ROOT" echo "" cd "$REPO_ROOT" echo "[1/5] Logging into Hugging Face..." echo "$HF_TOKEN" | hf auth login --token "$HF_TOKEN" echo "" echo "[2/5] Saving original README title..." original_readme_title="$(sed -n 's/^title: *//p' "$REPO_ROOT/README.md" | head -n 1)" update_readme_title "$SPACE_NAME" restore_readme_on_exit() { restore_readme_title "${original_readme_title:-HF Space}" } trap restore_readme_on_exit EXIT echo "" echo "[3/5] Ensuring Space exists..." hf repos create "$SPACE_REPO_ID" --repo-type space --space-sdk docker --exist-ok echo "" echo "[4/5] Uploading files to Space..." upload_tmp=$(mktemp) hf upload "$SPACE_REPO_ID" . --repo-type space \ --exclude '.git/**' \ --exclude '.git' \ --exclude '.github/**' \ --exclude 'logs/**' \ --exclude '*.log' \ --exclude 'docs/**' \ --exclude 'tests/**' \ --exclude 'LICENSE' \ --exclude '.dockerignore' \ --exclude '.python-version' \ --commit-message "fix: improve SSH service stability and backup.py error handling" 2>&1 | tee "$upload_tmp" upload_exit_code=${PIPESTATUS[0]} if [ $upload_exit_code -ne 0 ]; then echo "Error: Upload failed:" cat "$upload_tmp" rm -f "$upload_tmp" exit 1 fi rm -f "$upload_tmp" echo "" echo "[5/5] Requesting Space restart..." restart_tmp=$(mktemp) hf spaces restart "$SPACE_REPO_ID" 2>&1 | tee "$restart_tmp" restart_exit_code=${PIPESTATUS[0]} if [ $restart_exit_code -ne 0 ]; then echo "Warning: Restart request failed:" cat "$restart_tmp" fi rm -f "$restart_tmp" echo "" echo "============================================" echo "Done! Space updated: https://huggingface.co/spaces/$SPACE_REPO_ID" echo "============================================"