#!/bin/sh set -u say() { printf '%s\n' "$*" } warn() { printf '错误: %s\n' "$*" >&2 } usage() { cat <<'EOF' 用法: ./search_config.sh ./search_config.sh --tavily KEY --exa KEY --keenable KEY 选项: --tavily KEY 配置 TAVILY_API_KEY --exa KEY 配置 EXA_API_KEY --keenable KEY 配置 KEENABLE_API_KEY --config FILE 覆盖配置文件路径 -h, --help 显示帮助 不传 key 参数时进入隐藏输入的交互模式;传入任意 key 参数时不交互, 未指定的服务保留已有值。配置文件默认是 ~/.config/my-pi/search.env。 EOF } CONFIG_FILE=${MY_PI_SEARCH_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/my-pi/search.env} mode=interactive tavily_set=0 exa_set=0 keenable_set=0 tavily_value= exa_value= keenable_value= while [ "$#" -gt 0 ]; do case $1 in --tavily) [ "$#" -ge 2 ] || { warn "--tavily 缺少 KEY"; exit 2; } tavily_value=$2; tavily_set=1; mode=noninteractive; shift 2 ;; --exa) [ "$#" -ge 2 ] || { warn "--exa 缺少 KEY"; exit 2; } exa_value=$2; exa_set=1; mode=noninteractive; shift 2 ;; --keenable) [ "$#" -ge 2 ] || { warn "--keenable 缺少 KEY"; exit 2; } keenable_value=$2; keenable_set=1; mode=noninteractive; shift 2 ;; --config) [ "$#" -ge 2 ] || { warn "--config 缺少 FILE"; exit 2; } CONFIG_FILE=$2; shift 2 ;; -h|--help) usage; exit 0 ;; *) warn "未知参数:$1"; usage >&2; exit 2 ;; esac done valid_key() { case $1 in ''|*[!A-Za-z0-9._-]*) return 1 ;; *) return 0 ;; esac } read_existing() { existing_name=$1 [ -f "$CONFIG_FILE" ] || return 0 awk -v name="$existing_name" ' $1 == "export" { sub(/^export[ \t]+/, "") } index($0, name "=") == 1 { print substr($0, length(name) + 2); exit } ' "$CONFIG_FILE" } prompt_key() { prompt_label=$1 prompt_current=$2 if [ ! -t 0 ]; then warn "交互模式需要终端;请改用 --tavily/--exa/--keenable 参数。" return 2 fi if [ -n "$prompt_current" ]; then printf '%s API key(回车保留现有值): ' "$prompt_label" >&2 else printf '%s API key(回车跳过): ' "$prompt_label" >&2 fi stty -echo IFS= read -r prompt_value || prompt_status=$? stty echo printf '\n' >&2 [ "${prompt_status:-0}" -eq 0 ] || return "$prompt_status" printf '%s\n' "$prompt_value" } current_tavily=$(read_existing TAVILY_API_KEY) current_exa=$(read_existing EXA_API_KEY) current_keenable=$(read_existing KEENABLE_API_KEY) if [ "$mode" = interactive ]; then tavily_value=$(prompt_key Tavily "$current_tavily") || exit $? exa_value=$(prompt_key Exa "$current_exa") || exit $? keenable_value=$(prompt_key Keenable "$current_keenable") || exit $? [ -n "$tavily_value" ] && tavily_set=1 [ -n "$exa_value" ] && exa_set=1 [ -n "$keenable_value" ] && keenable_set=1 fi [ "$tavily_set" -eq 1 ] || tavily_value=$current_tavily [ "$exa_set" -eq 1 ] || exa_value=$current_exa [ "$keenable_set" -eq 1 ] || keenable_value=$current_keenable for key_value in "$tavily_value" "$exa_value" "$keenable_value"; do [ -z "$key_value" ] || valid_key "$key_value" || { warn "API key 只能包含字母、数字、点、下划线和连字符。" exit 2 } done config_dir=${CONFIG_FILE%/*} [ "$config_dir" = "$CONFIG_FILE" ] && config_dir=. umask 077 mkdir -p "$config_dir" || exit 1 config_tmp=$(mktemp "$config_dir/.search.env.XXXXXX") || exit 1 trap 'rm -f "$config_tmp"' EXIT HUP INT TERM { printf '# Generated by my-pi/search_config.sh. Do not commit this file.\n' [ -z "$tavily_value" ] || printf 'export TAVILY_API_KEY=%s\n' "$tavily_value" [ -z "$exa_value" ] || printf 'export EXA_API_KEY=%s\n' "$exa_value" [ -z "$keenable_value" ] || printf 'export KEENABLE_API_KEY=%s\n' "$keenable_value" } > "$config_tmp" || exit 1 chmod 600 "$config_tmp" || exit 1 mv "$config_tmp" "$CONFIG_FILE" || exit 1 trap - EXIT HUP INT TERM say "搜索 API key 已安全写入 ${CONFIG_FILE}(权限 600)。" say "重启 Pi 后生效;显式环境变量仍优先于该文件。"