Files
my-pi/install.sh
T
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> f237025cf9 fix(install.sh/update.sh): Homebrew 升级免确认并显式按 formula 升级
- Homebrew 7 默认进入 ask 模式,升级或安装带依赖时会停下等待 [y/n]。
  用户运行脚本(以及 install.sh 中逐项确认后)已构成授权,统一加
  HOMEBREW_NO_ASK=1;用环境变量而非 --yes,旧版 Homebrew 不会因未知
  参数报错。
- update.sh 的 formula 升级显式加 --formula:kotlin-lsp 来自
  jetbrains/utils tap,不加时 brew 会把短名当成 cask,报
  “Cask 'kotlin-lsp' is not installed” 而升级失败。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-19 02:47:09 -07:00

486 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
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
}
resolve_agent_dir() {
if [ -n "${PI_CODING_AGENT_DIR:-}" ]; then
case $PI_CODING_AGENT_DIR in
~/*) printf '%s\n' "$HOME/${PI_CODING_AGENT_DIR#~/}" ;;
*) printf '%s\n' "$PI_CODING_AGENT_DIR" ;;
esac
return 0
fi
printf '%s\n' "$HOME/.pi/agent"
}
# 整体删除并重建组合包拥有的 <agentDir>/my-pi/ 配置目录;脚本目录内的组合包副本即为来源。
rebuild_bundle_config() {
rebuild_helper=$SCRIPT_DIR/scripts/rebuild-bundle-config.mjs
if [ ! -f "$rebuild_helper" ]; then
warn "找不到组合包配置重建辅助脚本,已跳过配置重建。"
return 1
fi
if ! command -v node >/dev/null 2>&1; then
warn "找不到 node,无法重建组合包配置目录。"
return 1
fi
rebuild_agent_dir=$(resolve_agent_dir) || return 1
rebuild_status=$(node "$rebuild_helper" "$rebuild_agent_dir" 2>&1) || {
warn "组合包配置目录重建失败:$rebuild_status"
return 1
}
case $rebuild_status in
rebuilt:*) say "✓ 已重建组合包配置目录:${rebuild_status#rebuilt:}" ;;
*)
warn "组合包配置重建返回未知状态:$rebuild_status"
return 1
;;
esac
}
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
}
install_kotlin_lsp() {
require_brew || return 1
HOMEBREW_NO_ASK=1 brew install JetBrains/utils/kotlin-lsp || return 1
command -v kotlin-lsp >/dev/null 2>&1
}
install_java_21() {
require_brew || return 1
HOMEBREW_NO_ASK=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
HOMEBREW_NO_ASK=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
HOMEBREW_NO_ASK=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 组合包安装完成,开始部署受管配置。"
rebuild_bundle_config || record_install_failure "组合包配置重建"
say "开始检查终端环境和机器级 CLI 依赖。"
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"