#!/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 # ---- Ensure hf is in PATH (handles SSH_ASKPASS restricted env) ---- _HF_EXTRA_PATH="/root/.local/bin" if [[ ":$PATH:" != *":$_HF_EXTRA_PATH:"* ]]; then export PATH="$_HF_EXTRA_PATH:$PATH" 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: " 或 "Logged in as " _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? [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" }