page / scripts /_hf_user.sh
GGSheng's picture
feat: deploy to hf space | model=claude-22327b
31b91d7 verified
Raw
History Blame Contribute Delete
10.4 kB
#!/usr/bin/env bash
# ============================================================
# HF User helper - 提供 HF 账户切换和 token 获取的共享函数
#
# 提供以下公共 API(参照 bootstrap-hf.sh 的实现):
# - get_hf_username 获取当前 HF 用户名
# - read_current_hf_token 从 env / cache 文件读取 token
# - login_with_hf_token 用 token 登录 HF
# - prompt_hf_account_switch 检测/询问/切换 HF 账户
# - require_hf_token 必要时 prompt 后获取 token
# - prompt_yes_no y/n 询问(非 TTY 不阻塞)
# - prompt_line / prompt_required
# - prompt_secret_optional / prompt_secret_required
# - trim / info / warn / error / die
#
# 用法:
# source "$SCRIPT_DIR/_hf_user.sh"
# HF_TOKEN="$(require_hf_token)" || exit 1
# export HF_TOKEN
#
# 设计:
# - 所有 prompt 函数在 stdin 不是 TTY 时使用默认值 / 返回错误,
# 避免 c63f250 修复的"stdin 非 TTY 进入死循环"问题
# - login 成功后调用 hf auth whoami 验证,切换时输出 switched 信息
# - 不实现 RESTORE_HF_LOGIN_* 恢复机制(单命令工具不需要)
# ============================================================
# ---- Fix terminal for interactive input ----
if [[ -t 0 ]]; then
stty sane 2>/dev/null || true
fi
# ---- 通用辅助函数 ----
trim() {
printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
info() {
printf '%s\n' "$*"
}
warn() {
printf 'warning: %s\n' "$*" >&2
}
error() {
printf 'error: %s\n' "$*" >&2
}
die() {
error "$1"
exit 1
}
# ---- 输入提示 ----
prompt_line() {
local prompt="$1"
local default_value="${2:-}"
local value=""
if [[ -t 0 ]]; then
if [[ -n "$default_value" ]]; then
read -r -p "$prompt [$default_value]: " value || true
else
read -r -p "$prompt: " value || true
fi
value="$(printf '%s' "$value" | tr -d '\r\n')"
if [[ -z "$value" ]]; then
value="$default_value"
fi
else
value="$default_value"
fi
printf '%s' "$value"
}
prompt_required() {
local prompt="$1"
local default_value="${2:-}"
local value=""
while [[ -z "$value" ]]; do
value="$(prompt_line "$prompt" "$default_value")"
if [[ -z "$value" ]]; then
if [[ -t 0 ]]; then
printf 'This value is required.\n' >&2
else
# 非 TTY 模式无法补全,直接返回空(调用方应 die)
printf '%s' ""
return 1
fi
fi
done
printf '%s' "$value"
}
prompt_secret_optional() {
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
$'\177'|$'\b')
if [[ -n "$value" ]]; then
value="${value%?}"
printf '\b \b' >&2
fi
;;
*)
value+="$char"
printf '*' >&2
;;
esac
done
printf '\n' >&2
else
# 非 TTY:直接读一行(无回显),不阻塞
read -r -p "$prompt: " value || true
fi
printf '%s' "$(trim "${value:-}")"
}
prompt_secret_required() {
local prompt="$1"
local value=""
while [[ -z "$value" ]]; do
value="$(prompt_secret_optional "$prompt")"
value="$(trim "$value")"
if [[ -z "$value" ]]; then
if [[ -t 0 ]]; then
printf 'This value is required.\n' >&2
else
# 非 TTY: 不阻塞,返回空
printf '%s' ""
return 1
fi
fi
done
printf '%s' "$value"
}
# y/n 询问
# $1: prompt 文本
# $2: 默认值 "y" 或 "n"(默认 n)
# 输出: "yes" 或 "no"
# 非 TTY 时直接使用默认值,不阻塞
prompt_yes_no() {
local prompt="$1"
local default_value="${2:-n}"
local answer=""
local hint="[y/N]"
if [[ "$default_value" == "y" ]]; then
hint="[Y/n]"
fi
if [[ -t 0 ]]; then
while true; do
read -r -p "$prompt $hint: " answer || true
answer="$(printf '%s' "$answer" | tr -d '\r\n' | tr '[:upper:]' '[:lower:]')"
if [[ -z "$answer" ]]; then
answer="$default_value"
fi
case "$answer" in
y|yes)
printf 'yes'
return 0
;;
n|no)
printf 'no'
return 0
;;
*)
printf 'Please answer y or n.\n' >&2
;;
esac
done
else
# 非 TTY:使用默认值,不阻塞
if [[ "$default_value" == "y" ]]; then
printf 'yes'
else
printf 'no'
fi
fi
}
# ---- Token 处理 ----
# 解析 whoami 输出,提取 username
# 接受两种格式: "user: <name>" 或 "Logged in as <name>"
_parse_whoami_username() {
local output="$1"
local username
username="$(printf '%s\n' "$output" | sed -nE 's/^[[:space:]]*user:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n 1)"
if [[ -z "$username" ]]; then
username="$(printf '%s\n' "$output" | sed -nE 's/.*[Ll]ogged in as[[:space:]]+([^[:space:]]+).*/\1/p' | head -n 1)"
fi
printf '%s' "$username"
}
# 读取当前可用的 token
# 优先级: HUGGINGFACE_HUB_TOKEN > HF_TOKEN > HF_TOKEN_FILE > ~/.cache/huggingface/token
read_current_hf_token() {
if [[ -n "${HUGGINGFACE_HUB_TOKEN:-}" ]]; then
printf '%s' "${HUGGINGFACE_HUB_TOKEN}" | tr -d '\r\n'
return 0
fi
if [[ -n "${HF_TOKEN:-}" ]]; then
printf '%s' "${HF_TOKEN}" | tr -d '\r\n'
return 0
fi
local token_file="${HF_TOKEN_FILE:-$HOME/.cache/huggingface/token}"
if [[ -f "$token_file" ]]; then
head -n 1 "$token_file" | tr -d '\r\n'
return 0
fi
printf ''
}
# 用 token 登录 HF
login_with_hf_token() {
local token="$1"
hf auth login --token "$token" >/dev/null 2>&1 || die "hf auth login with token failed."
}
# ---- 核心 API ----
# 获取当前 HF 用户名
# 返回: HF username 或 空字符串
# 实现与之前 _hf_user.sh:17-61 一致
get_hf_username() {
local whoami_output
local username
# 方法1: 通过 hf auth whoami
whoami_output="$(hf auth whoami 2>&1 || true)"
username="$(_parse_whoami_username "$whoami_output")"
if [[ -n "$username" ]]; then
printf '%s' "$username"
return 0
fi
# 方法2: 通过 Python huggingface_hub(fallback)
local hf_token_file="${HF_TOKEN_FILE:-$HOME/.cache/huggingface/token}"
local token_from_cache=""
if [[ -f "$hf_token_file" ]]; then
token_from_cache="$(head -n 1 "$hf_token_file" | tr -d '\r\n')"
fi
if [[ -n "$token_from_cache" ]]; then
username="$(
HF_API_TOKEN="$token_from_cache" python3 - <<'PY' 2>/dev/null || true
from huggingface_hub import HfApi
import os
token = (os.environ.get("HF_API_TOKEN") or "").strip() or None
api = HfApi(token=token)
data = api.whoami(token=token)
name = data.get("name", "") if isinstance(data, dict) else ""
print((name or "").strip())
PY
)"
username="$(printf '%s' "$username" | tr -d '\r\n')"
if [[ -n "$username" ]]; then
printf '%s' "$username"
return 0
fi
fi
return 1
}
# HF 账户切换提示
# 已登录时:
# - TTY: 询问 "Use this user? <name> [Y/n]",如拒绝则 prompt 新 token 并切换
# - 非 TTY: 静默使用当前用户
# 未登录时:
# - return 1
prompt_hf_account_switch() {
local whoami_output
local current_username
whoami_output="$(hf auth whoami 2>&1 || true)"
current_username="$(_parse_whoami_username "$whoami_output")"
if [[ -z "$current_username" ]]; then
return 1
fi
if [[ -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
# 切换账户
while true; do
local new_token
new_token="$(prompt_secret_required "HF_TOKEN to switch to a different account")" || {
warn "Empty token, please try again or press Ctrl+C to cancel"
continue
}
if [[ -z "$new_token" ]]; then
warn "Empty token, please try again or press Ctrl+C to cancel"
continue
fi
if ! hf auth login --token "$new_token" >/dev/null 2>&1; then
warn "HF login failed, please try again"
continue
fi
local verify_output
verify_output="$(hf auth whoami 2>&1 || true)"
local new_username
new_username="$(_parse_whoami_username "$verify_output")"
if [[ -n "$new_username" && "$new_username" != "$current_username" ]]; then
info "HF account switched: $current_username -> $new_username"
elif [[ -n "$new_username" ]]; then
info "HF login successful as: $new_username"
else
info "HF login successful"
fi
return 0
done
fi
return 0
}
# 获取 HF token,必要时提示用户登录
# 优先级:
# 1. prompt_hf_account_switch(已登录则用当前;TTY 下可切换)
# 2. env (HUGGINGFACE_HUB_TOKEN / HF_TOKEN)
# 3. ~/.cache/huggingface/token
# 4. TTY 下交互式 prompt
# 5. die
# 成功时通过 stdout 输出 token
require_hf_token() {
local token=""
# 1. 尝试切换(如果已登录)
if prompt_hf_account_switch; then
token="$(read_current_hf_token)"
[[ -n "$token" ]] && { printf '%s' "$token"; return 0; }
fi
# 2. 回退到环境变量 / cache 文件
token="$(read_current_hf_token)"
[[ -n "$token" ]] && { printf '%s' "$token"; return 0; }
# 3. 最后手段: 交互式 prompt
if [[ -t 0 ]]; then
info "HF CLI is not logged in and HF_TOKEN is not set."
while true; do
local new_token
new_token="$(prompt_secret_required "HF_TOKEN (required for HF login)")" || {
# 非 TTY 或 Ctrl+C
break
}
if [[ -z "$new_token" ]]; then
warn "Empty token, please try again or press Ctrl+C to cancel"
continue
fi
if ! hf auth login --token "$new_token" >/dev/null 2>&1; then
warn "HF login failed, please try again"
continue
fi
local verify_output
verify_output="$(hf auth whoami 2>&1 || true)"
local new_username
new_username="$(_parse_whoami_username "$verify_output")"
if [[ -n "$new_username" ]]; then
info "HF login successful as: $new_username"
else
info "HF login successful"
fi
printf '%s' "$new_token"
return 0
done
fi
die "HF_TOKEN is required. Set HF_TOKEN env var or run: hf auth login"
}