1. HIGH-1: onboarding ↔ kei-model-router связка
До: onboarding мастер писал ~/.claude/config/onboarding.toml,
но router его не читал — выбор провайдера декоративный.
После: lib-onboarding.sh::onboarding_write_config доп. пишет
~/.claude/config/user-model-override.toml; registry.rs::Registry
получил load_user_override() возвращающий UserModelOverride.
Приоритет: --pinned > user-override > agent-profiles default_model_ref.
2 новых теста (round-trip TOML, optional transport).
2. HIGH-2: eval "$install_cmd" → bash -c "$install_cmd"
До: lib-preflight.sh::preflight_offer_install делал eval.
После: bash -c с явным subshell + печать команды юзеру до запуска.
3. HIGH-3: codex.sh regex false-pass
До: grep -qiE "logged.in|active" пропускал "not logged in" как pass.
После: сначала negative-pattern (not logged|signed out|please log in),
потом positive (\blogged in\b|status: active|auth: yes).
4. HIGH-4: path traversal в source preflight
До: lib-preflight.sh::preflight_run делал source без валидации
provider id — `../../../evil` сработал бы.
После: whitelist regex ^[a-z0-9][a-z0-9_-]{0,63}$ + realpath
проверка что resolved путь не вышел за PREFLIGHT_DIR.
5. HIGH-5: curl|sh без verification
ollama-local.sh + google-vertex.sh теперь печатают предупреждение
что Linux-установка тянет shell-скрипт с внешнего сервера без
проверки хэша/подписи, и предлагают альтернативу.
MEDIUM попутно:
- anthropic-bedrock.sh: один вызов aws sts get-caller-identity
вместо двух (экономит 1-3с), различает cred-error от network
по тексту stderr, маскирует account ID в ARN перед печатью.
- mlx-local.sh: pip install --user mlx-lm вместо global pip install
(не требует sudo, не загрязняет system Python).
Тесты: cargo test --lib 80/80, bash -n всех изменённых файлов чисто.
78 lines
3.2 KiB
Bash
78 lines
3.2 KiB
Bash
# shellcheck shell=bash
|
|
# lib-preflight.sh — диспетчер preflight-проверок CLI.
|
|
#
|
|
# Контракт:
|
|
# preflight_run <provider-id>
|
|
# 1. Ищет файл install/preflight/<provider-id>.sh
|
|
# 2. Если есть — source'ит и вызывает `preflight_check_<sanitized-id>`
|
|
# 3. Функция возвращает 0 (ok) / 1 (missing, инструкция напечатана)
|
|
# 4. Если файла нет — провайдеру CLI не нужен, тихо exit 0
|
|
#
|
|
# Файл per-provider должен экспортировать ОДНУ функцию:
|
|
# preflight_check_<id>() — печатает инструкцию в stderr, exit 0/1
|
|
#
|
|
# Sanitize: dashes в id заменяются на underscores для имени функции
|
|
# (bash не любит dashes в идентификаторах).
|
|
|
|
PREFLIGHT_DIR="${LIB_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}/preflight"
|
|
|
|
# Печатает инструкцию по установке, спрашивает действие.
|
|
# Аргументы: $1 — имя CLI, $2 — команда установки.
|
|
preflight_offer_install() {
|
|
local cli="$1"
|
|
local install_cmd="$2"
|
|
echo "" >&2
|
|
echo " ⚠ $cli не найден." >&2
|
|
echo " Установить: $install_cmd" >&2
|
|
echo "" >&2
|
|
if [ -t 0 ] && [ -t 1 ]; then
|
|
echo " ⓘ команда: $install_cmd" >&2
|
|
read -r -p " Поставить сейчас? [y/N/skip] " ans
|
|
case "$ans" in
|
|
y|Y|yes)
|
|
# bash -c вместо eval — explicit subshell, не word-splitting'тся
|
|
# лишний раз в текущем процессе.
|
|
bash -c "$install_cmd"
|
|
return $?
|
|
;;
|
|
skip|s|S)
|
|
echo " пропускаю — поставите вручную позже." >&2
|
|
return 0
|
|
;;
|
|
*)
|
|
echo " пропуск (по умолчанию)." >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
# non-TTY: только печатаем инструкцию.
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Главный диспетчер. Вызывается из onboarding между pick_model и collect_auth.
|
|
preflight_run() {
|
|
local provider="$1"
|
|
[ -z "$provider" ] && return 0
|
|
# Whitelist символов в provider-id: только [a-z0-9_-], длина 1..64.
|
|
# Защищает от path-traversal (../) и shell-инъекций через имя файла.
|
|
if ! [[ "$provider" =~ ^[a-z0-9][a-z0-9_-]{0,63}$ ]]; then
|
|
echo " ⚠ preflight: provider id '$provider' содержит недопустимые символы — пропуск" >&2
|
|
return 0
|
|
fi
|
|
local script="$PREFLIGHT_DIR/${provider}.sh"
|
|
# Проверяем что resolved путь не вышел за PREFLIGHT_DIR (на случай symlink'ов).
|
|
local resolved
|
|
resolved="$(cd "$PREFLIGHT_DIR" 2>/dev/null && pwd -P)/${provider}.sh"
|
|
if [ ! -f "$script" ] || [ ! -f "$resolved" ]; then
|
|
return 0 # CLI не нужен — direct-api, ключ собирается ниже
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$script"
|
|
local fn="preflight_check_${provider//-/_}"
|
|
if command -v "$fn" >/dev/null 2>&1; then
|
|
"$fn"
|
|
return $?
|
|
fi
|
|
return 0
|
|
}
|