mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
506 lines
15 KiB
Bash
Executable File
506 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
||
|
||
set -u
|
||
|
||
SCRIPT_DIR=$(CDPATH=; cd "$(dirname "$0")" && pwd) || exit 1
|
||
MANAGED_CONFIG_HELPERS=$SCRIPT_DIR/scripts/managed-config.sh
|
||
if [ ! -f "$MANAGED_CONFIG_HELPERS" ]; then
|
||
printf '错误: 找不到受管配置辅助脚本:%s\n' "$MANAGED_CONFIG_HELPERS" >&2
|
||
exit 1
|
||
fi
|
||
# shellcheck source=scripts/managed-config.sh
|
||
. "$MANAGED_CONFIG_HELPERS"
|
||
|
||
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
|
||
POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh
|
||
HIPPO_MEMORY_VERSION_FILE=$SCRIPT_DIR/config/hippo-memory-version
|
||
CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh
|
||
KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh
|
||
OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
||
POWERLEVEL10K_REPOSITORY=https://github.com/romkatv/powerlevel10k.git
|
||
ZSH_AUTOSUGGESTIONS_REPOSITORY=https://github.com/zsh-users/zsh-autosuggestions.git
|
||
ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY=https://github.com/zsh-users/zsh-syntax-highlighting.git
|
||
install_failed=0
|
||
|
||
say() {
|
||
printf '%s\n' "$*"
|
||
}
|
||
|
||
warn() {
|
||
printf '警告: %s\n' "$*" >&2
|
||
}
|
||
|
||
ask_yes_no() {
|
||
prompt=$1
|
||
printf '%s [y/N] ' "$prompt"
|
||
if ! IFS= read -r answer; then
|
||
answer=n
|
||
fi
|
||
case $answer in
|
||
y|Y|yes|YES|Yes) return 0 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
ask_to_install() {
|
||
ask_yes_no "未检测到 $1。是否现在安装?"
|
||
}
|
||
|
||
ask_to_enable() {
|
||
ask_yes_no "尚未启用 $1。是否现在安装所需文件并启用?"
|
||
}
|
||
|
||
require_brew() {
|
||
if command -v brew >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
|
||
warn "自动安装该依赖需要 Homebrew,但当前 PATH 中没有 brew。"
|
||
warn "请先安装 Homebrew(https://brew.sh/),然后重新运行本脚本。"
|
||
return 1
|
||
}
|
||
|
||
record_install_failure() {
|
||
warn "$1 安装失败;其余依赖仍会继续检查。"
|
||
install_failed=1
|
||
}
|
||
|
||
java_major_version() {
|
||
java_version=$(java -version 2>&1 | awk -F '"' 'NR == 1 { print $2 }')
|
||
case $java_version in
|
||
1.*) java_version=${java_version#1.} ;;
|
||
esac
|
||
java_major=${java_version%%.*}
|
||
case $java_major in
|
||
''|*[!0-9]*) return 1 ;;
|
||
*) printf '%s\n' "$java_major" ;;
|
||
esac
|
||
}
|
||
|
||
install_codegraph() {
|
||
if ! command -v curl >/dev/null 2>&1; then
|
||
warn "安装 CodeGraph 需要 curl。"
|
||
return 1
|
||
fi
|
||
curl -fsSL "$CODEGRAPH_INSTALL_URL" | sh || return 1
|
||
|
||
# 官方安装器默认使用 ~/.local/bin;让本次检查立即看到新命令。
|
||
if [ -d "$HOME/.local/bin" ]; then
|
||
PATH="$HOME/.local/bin:$PATH"
|
||
export PATH
|
||
fi
|
||
command -v codegraph >/dev/null 2>&1
|
||
}
|
||
|
||
read_hippo_memory_version() {
|
||
if [ ! -f "$HIPPO_MEMORY_VERSION_FILE" ]; then
|
||
warn "找不到 Hippo Memory 版本文件:$HIPPO_MEMORY_VERSION_FILE"
|
||
return 1
|
||
fi
|
||
hippo_target_version=$(sed -n '1p' "$HIPPO_MEMORY_VERSION_FILE")
|
||
case $hippo_target_version in
|
||
''|*[!0-9.]*)
|
||
warn "Hippo Memory 版本文件内容无效:$hippo_target_version"
|
||
return 1
|
||
;;
|
||
esac
|
||
printf '%s\n' "$hippo_target_version"
|
||
}
|
||
|
||
installed_hippo_memory_version() {
|
||
hippo --version 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9.]*\).*$/\1/p' | sed -n '1p'
|
||
}
|
||
|
||
install_hippo_memory() {
|
||
if ! command -v npm >/dev/null 2>&1; then
|
||
warn "安装 Hippo Memory CLI 需要 npm。"
|
||
return 1
|
||
fi
|
||
hippo_target_version=$(read_hippo_memory_version) || return 1
|
||
npm install -g "hippo-memory@$hippo_target_version" || return 1
|
||
if ! command -v hippo >/dev/null 2>&1; then
|
||
warn "npm 已完成安装,但当前 PATH 中仍找不到 hippo。"
|
||
return 1
|
||
fi
|
||
hippo_installed_version=$(installed_hippo_memory_version)
|
||
if [ "$hippo_installed_version" != "$hippo_target_version" ]; then
|
||
warn "Hippo Memory CLI 版本校验失败:期望 $hippo_target_version,实际 ${hippo_installed_version:-未知}。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
install_kotlin_lsp() {
|
||
require_brew || return 1
|
||
brew install JetBrains/utils/kotlin-lsp || return 1
|
||
command -v kotlin-lsp >/dev/null 2>&1
|
||
}
|
||
|
||
install_java_21() {
|
||
require_brew || return 1
|
||
brew install openjdk@21 || return 1
|
||
java_home=$(brew --prefix openjdk@21 2>/dev/null) || return 1
|
||
PATH="$java_home/bin:$PATH"
|
||
export PATH
|
||
installed_java_major=$(java_major_version || true)
|
||
if [ -z "$installed_java_major" ] || [ "$installed_java_major" -lt 21 ]; then
|
||
return 1
|
||
fi
|
||
say "已在本次安装流程中使用 $java_home/bin。若新终端仍找不到 Java,请把该目录加入 PATH。"
|
||
}
|
||
|
||
install_jdtls() {
|
||
require_brew || return 1
|
||
brew install jdtls || return 1
|
||
command -v jdtls >/dev/null 2>&1
|
||
}
|
||
|
||
detect_kitty() {
|
||
kitty_bin=
|
||
if command -v kitty >/dev/null 2>&1; then
|
||
kitty_bin=$(command -v kitty)
|
||
return 0
|
||
fi
|
||
for candidate in /Applications/kitty.app/Contents/MacOS/kitty "$HOME/.local/kitty.app/bin/kitty"; do
|
||
if [ -x "$candidate" ]; then
|
||
kitty_bin=$candidate
|
||
return 0
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
install_kitty() {
|
||
case $(uname -s) in
|
||
Darwin)
|
||
require_brew || return 1
|
||
brew install --cask kitty || return 1
|
||
;;
|
||
*)
|
||
if ! command -v curl >/dev/null 2>&1; then
|
||
warn "安装 Kitty 需要 curl。"
|
||
return 1
|
||
fi
|
||
curl -L "$KITTY_INSTALL_URL" | sh /dev/stdin || return 1
|
||
;;
|
||
esac
|
||
detect_kitty
|
||
}
|
||
|
||
resolve_kitty_config_dir() {
|
||
kitty_config_dir=${KITTY_CONFIG_DIRECTORY:-${XDG_CONFIG_HOME:-$HOME/.config}/kitty}
|
||
}
|
||
|
||
kitty_solarized_dark_configured() {
|
||
resolve_kitty_config_dir
|
||
[ -f "$kitty_config_dir/current-theme.conf" ] || return 1
|
||
[ -f "$kitty_config_dir/kitty.conf" ] || return 1
|
||
grep -F "Solarized Dark" "$kitty_config_dir/current-theme.conf" >/dev/null 2>&1 || return 1
|
||
grep -E '^[[:space:]]*include[[:space:]]+([^#[:space:]]*/)?current-theme\.conf([[:space:]]*(#.*)?)?$' \
|
||
"$kitty_config_dir/kitty.conf" >/dev/null 2>&1
|
||
}
|
||
|
||
configure_kitty_solarized_dark() {
|
||
"$kitty_bin" +kitten themes --reload-in=none "Solarized Dark"
|
||
}
|
||
|
||
resolve_zsh_paths() {
|
||
oh_my_zsh_dir=${ZSH:-$HOME/.oh-my-zsh}
|
||
zsh_custom_dir=${ZSH_CUSTOM:-$oh_my_zsh_dir/custom}
|
||
zshrc_file=${ZDOTDIR:-$HOME}/.zshrc
|
||
my_pi_config_dir=${XDG_CONFIG_HOME:-$HOME/.config}/my-pi
|
||
p10k_managed_config_file=$my_pi_config_dir/p10k.zsh
|
||
}
|
||
|
||
oh_my_zsh_installed() {
|
||
resolve_zsh_paths
|
||
[ -f "$oh_my_zsh_dir/oh-my-zsh.sh" ]
|
||
}
|
||
|
||
install_oh_my_zsh() {
|
||
if ! command -v curl >/dev/null 2>&1; then
|
||
warn "安装 Oh My Zsh 需要 curl。"
|
||
return 1
|
||
fi
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
warn "安装 Oh My Zsh 需要 git。"
|
||
return 1
|
||
fi
|
||
resolve_zsh_paths
|
||
oh_my_zsh_installer=$(curl -fsSL "$OH_MY_ZSH_INSTALL_URL") || return 1
|
||
RUNZSH=no CHSH=no KEEP_ZSHRC=yes ZSH="$oh_my_zsh_dir" \
|
||
sh -c "$oh_my_zsh_installer" "" --unattended --keep-zshrc || return 1
|
||
oh_my_zsh_installed
|
||
}
|
||
|
||
prepare_zshrc() {
|
||
resolve_zsh_paths
|
||
zshrc_dir=${zshrc_file%/*}
|
||
[ "$zshrc_dir" = "$zshrc_file" ] && zshrc_dir=.
|
||
mkdir -p "$zshrc_dir" || return 1
|
||
[ -f "$zshrc_file" ] || : > "$zshrc_file" || return 1
|
||
}
|
||
|
||
replace_zsh_managed_block() {
|
||
managed_id=$1
|
||
managed_placement=$2
|
||
managed_anchor=$3
|
||
shift 3
|
||
managed_content=$(mktemp "${TMPDIR:-/tmp}/my-pi-zsh-block.XXXXXX") || return 1
|
||
printf '%s\n' "$@" > "$managed_content"
|
||
my_pi_replace_managed_block "$zshrc_file" "$managed_id" "$managed_content" \
|
||
"$managed_placement" "$managed_anchor"
|
||
managed_status=$?
|
||
rm -f "$managed_content"
|
||
return "$managed_status"
|
||
}
|
||
|
||
zshrc_has_oh_my_zsh_loader() {
|
||
[ -f "$zshrc_file" ] || return 1
|
||
grep -E '^[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh' "$zshrc_file" >/dev/null 2>&1
|
||
}
|
||
|
||
ensure_oh_my_zsh_loader() {
|
||
prepare_zshrc || return 1
|
||
if zshrc_has_oh_my_zsh_loader; then
|
||
return 0
|
||
fi
|
||
replace_zsh_managed_block "oh-my-zsh-loader" "append" "" \
|
||
"export ZSH=\"$oh_my_zsh_dir\"" \
|
||
"source \"\$ZSH/oh-my-zsh.sh\""
|
||
}
|
||
|
||
set_powerlevel10k_theme() {
|
||
prepare_zshrc || return 1
|
||
replace_zsh_managed_block "powerlevel10k-theme" "before" \
|
||
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
|
||
'ZSH_THEME="powerlevel10k/powerlevel10k"'
|
||
}
|
||
|
||
p10k_config_sourced() {
|
||
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-config"
|
||
}
|
||
|
||
ensure_p10k_config_source() {
|
||
prepare_zshrc || return 1
|
||
replace_zsh_managed_block "powerlevel10k-config" "append" "" \
|
||
"[[ ! -f \"$p10k_managed_config_file\" ]] || source \"$p10k_managed_config_file\""
|
||
}
|
||
|
||
configure_powerlevel10k() {
|
||
resolve_zsh_paths
|
||
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
|
||
if [ ! -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ]; then
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
warn "安装 Powerlevel10k 需要 git。"
|
||
return 1
|
||
fi
|
||
mkdir -p "$zsh_custom_dir/themes" || return 1
|
||
git clone --depth=1 "$POWERLEVEL10K_REPOSITORY" "$powerlevel10k_dir" || return 1
|
||
fi
|
||
|
||
my_pi_sync_managed_file "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" || return 1
|
||
set_powerlevel10k_theme || return 1
|
||
ensure_oh_my_zsh_loader || return 1
|
||
ensure_p10k_config_source || return 1
|
||
say "已应用仓库内置的 Powerlevel10k Rainbow/ASCII 受管配置。"
|
||
}
|
||
|
||
zsh_plugin_enabled() {
|
||
plugin=$1
|
||
[ -f "$zshrc_file" ] || return 1
|
||
awk -v target="$plugin" '
|
||
{
|
||
line = $0
|
||
sub(/[[:space:]]*#.*/, "", line)
|
||
if (line ~ /^[[:space:]]*plugins[[:space:]]*[+]?=/) active = 1
|
||
if (active) {
|
||
gsub(/[()=+]/, " ", line)
|
||
count = split(line, fields, /[[:space:]]+/)
|
||
for (i = 1; i <= count; i++) if (fields[i] == target) found = 1
|
||
if ($0 ~ /\)/) active = 0
|
||
}
|
||
}
|
||
END { exit(found ? 0 : 1) }
|
||
' "$zshrc_file"
|
||
}
|
||
|
||
add_zsh_plugin() {
|
||
plugin=$1
|
||
prepare_zshrc || return 1
|
||
ensure_oh_my_zsh_loader || return 1
|
||
replace_zsh_managed_block "zsh-plugin-$plugin" "before" \
|
||
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
|
||
"plugins+=($plugin)"
|
||
}
|
||
|
||
install_and_enable_zsh_plugin() {
|
||
plugin=$1
|
||
repository=${2:-}
|
||
if [ -n "$repository" ] && [ ! -d "$zsh_custom_dir/plugins/$plugin" ]; then
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
warn "安装 $plugin 需要 git。"
|
||
return 1
|
||
fi
|
||
mkdir -p "$zsh_custom_dir/plugins" || return 1
|
||
git clone --depth=1 "$repository" "$zsh_custom_dir/plugins/$plugin" || return 1
|
||
fi
|
||
add_zsh_plugin "$plugin"
|
||
}
|
||
|
||
check_zsh_plugin() {
|
||
plugin=$1
|
||
description=$2
|
||
repository=${3:-}
|
||
if zsh_plugin_enabled "$plugin"; then
|
||
say "✓ $plugin 已启用"
|
||
elif ask_to_enable "$description"; then
|
||
install_and_enable_zsh_plugin "$plugin" "$repository" || record_install_failure "$plugin"
|
||
else
|
||
say "- 已跳过 ${plugin}。"
|
||
fi
|
||
}
|
||
|
||
if ! command -v pi >/dev/null 2>&1; then
|
||
warn "找不到 pi,无法安装组合包。请先安装 Pi 并确保 pi 位于 PATH。"
|
||
exit 127
|
||
fi
|
||
|
||
say "正在安装 Pi 组合包:$PACKAGE_SOURCE"
|
||
if ! pi install "$PACKAGE_SOURCE"; then
|
||
warn "pi install 执行失败,已停止后续依赖检查。"
|
||
exit 1
|
||
fi
|
||
|
||
say "Pi 组合包安装完成,开始检查终端环境和机器级 CLI 依赖。"
|
||
|
||
if command -v hippo >/dev/null 2>&1; then
|
||
hippo_installed_version=$(installed_hippo_memory_version)
|
||
hippo_target_version=$(read_hippo_memory_version || true)
|
||
if [ -n "$hippo_installed_version" ] && [ "$hippo_installed_version" = "$hippo_target_version" ]; then
|
||
say "✓ Hippo Memory CLI $hippo_installed_version 已安装"
|
||
else
|
||
say "✓ Hippo Memory CLI ${hippo_installed_version:-未知版本} 已安装;组合包目标版本为 ${hippo_target_version:-未知},可运行 update.sh 同步。"
|
||
fi
|
||
elif ask_to_install "Hippo Memory CLI(官方 Pi 记忆扩展运行时)"; then
|
||
if install_hippo_memory; then
|
||
say "✓ Hippo Memory CLI 安装完成"
|
||
else
|
||
record_install_failure "Hippo Memory CLI"
|
||
fi
|
||
else
|
||
say "- 已跳过 Hippo Memory CLI;官方 Hippo Pi 扩展本次将不可用。"
|
||
fi
|
||
|
||
if command -v hippo >/dev/null 2>&1; then
|
||
say "- 未自动运行 hippo init;请由用户在需要启用记忆的项目目录中自行执行。"
|
||
fi
|
||
|
||
kitty_ok=0
|
||
if detect_kitty; then
|
||
kitty_ok=1
|
||
say "✓ Kitty 已安装(${kitty_bin})"
|
||
elif ask_to_install "Kitty 终端"; then
|
||
if install_kitty; then
|
||
kitty_ok=1
|
||
say "✓ Kitty 安装完成"
|
||
else
|
||
record_install_failure "Kitty"
|
||
fi
|
||
else
|
||
say "- 已跳过 Kitty;不会检查 Solarized Dark 主题。"
|
||
fi
|
||
|
||
if [ "$kitty_ok" -eq 1 ]; then
|
||
if kitty_solarized_dark_configured; then
|
||
say "✓ Kitty 已配置 Solarized Dark"
|
||
elif ask_yes_no "Kitty 尚未配置 Solarized Dark。是否现在配置?"; then
|
||
configure_kitty_solarized_dark || record_install_failure "Kitty Solarized Dark 主题"
|
||
else
|
||
say "- 已跳过 Kitty Solarized Dark 主题。"
|
||
fi
|
||
fi
|
||
|
||
oh_my_zsh_ok=0
|
||
if oh_my_zsh_installed; then
|
||
oh_my_zsh_ok=1
|
||
say "✓ Oh My Zsh 已安装(${oh_my_zsh_dir})"
|
||
elif ask_to_install "Oh My Zsh"; then
|
||
if install_oh_my_zsh; then
|
||
oh_my_zsh_ok=1
|
||
say "✓ Oh My Zsh 安装完成"
|
||
else
|
||
record_install_failure "Oh My Zsh"
|
||
fi
|
||
else
|
||
say "- 已跳过 Oh My Zsh;不会检查 Powerlevel10k 和 Zsh 插件。"
|
||
fi
|
||
|
||
if [ "$oh_my_zsh_ok" -eq 1 ]; then
|
||
resolve_zsh_paths
|
||
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
|
||
if [ -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ] && \
|
||
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-theme" && \
|
||
[ -f "$p10k_managed_config_file" ] && \
|
||
cmp -s "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" && p10k_config_sourced; then
|
||
say "✓ Powerlevel10k 已安装并配置"
|
||
elif ask_yes_no "Powerlevel10k 尚未完整配置。是否安装并应用仓库内置的 Rainbow/ASCII 配置?"; then
|
||
configure_powerlevel10k || record_install_failure "Powerlevel10k"
|
||
else
|
||
say "- 已跳过 Powerlevel10k。"
|
||
fi
|
||
|
||
check_zsh_plugin "git" "git 插件" ""
|
||
check_zsh_plugin "zsh-autosuggestions" "zsh-autosuggestions(命令自动建议)" \
|
||
"$ZSH_AUTOSUGGESTIONS_REPOSITORY"
|
||
check_zsh_plugin "zsh-syntax-highlighting" "zsh-syntax-highlighting(语法高亮)" \
|
||
"$ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY"
|
||
fi
|
||
|
||
if command -v codegraph >/dev/null 2>&1; then
|
||
say "✓ codegraph 已安装"
|
||
elif ask_to_install "CodeGraph(提供 codegraph_explore)"; then
|
||
install_codegraph || record_install_failure "CodeGraph"
|
||
else
|
||
say "- 已跳过 CodeGraph;codegraph_explore 将不可用。"
|
||
fi
|
||
|
||
if command -v kotlin-lsp >/dev/null 2>&1; then
|
||
say "✓ kotlin-lsp 已安装"
|
||
elif ask_to_install "kotlin-lsp(Kotlin LSP)"; then
|
||
install_kotlin_lsp || record_install_failure "kotlin-lsp"
|
||
else
|
||
say "- 已跳过 kotlin-lsp;Kotlin LSP 功能将不可用。"
|
||
fi
|
||
|
||
java_ok=0
|
||
if command -v java >/dev/null 2>&1; then
|
||
java_major=$(java_major_version || true)
|
||
if [ -n "$java_major" ] && [ "$java_major" -ge 21 ]; then
|
||
java_ok=1
|
||
say "✓ Java $java_major 已安装(满足 Java 21+)"
|
||
fi
|
||
fi
|
||
|
||
if [ "$java_ok" -eq 0 ]; then
|
||
if ask_to_install "Java 21+(JDT LS 运行时)"; then
|
||
if install_java_21; then
|
||
java_ok=1
|
||
else
|
||
record_install_failure "Java 21+"
|
||
fi
|
||
else
|
||
say "- 已跳过 Java 21+;JDT LS 可能无法启动。"
|
||
fi
|
||
fi
|
||
|
||
if command -v jdtls >/dev/null 2>&1; then
|
||
say "✓ jdtls 已安装"
|
||
elif ask_to_install "jdtls(Java LSP)"; then
|
||
install_jdtls || record_install_failure "jdtls"
|
||
else
|
||
say "- 已跳过 jdtls;Java LSP 功能将不可用。"
|
||
fi
|
||
|
||
say "终端环境与依赖检查完成。RTK 命令改写默认关闭,因此未检查可选的 rtk CLI。"
|
||
exit "$install_failed"
|