feat: 完成 Zsh 插件配置脚本的优化和测试 - 修复 autojump 配置冲突 - 清理重复的插件安装 - 优化脚本结构和注释 feat: 优化shell脚本 - 1. 增强错误处理和恢复机制 2. 添加网络连接优化和镜像源支持 3. 改进进度显示和用户交互 4. 优化配置文件管理和备份 5. 改进插件管理机制 6. 增强依赖检查和安装 7. 添加完整的日志记录功能 8. 修复字体安装相关问题 docs: 完善shell、system和utils部分的README文档 - 1. 添加详细的脚本说明和使用方法 2. 补充依赖要求和注意事项 3. 添加常见问题解答 4. 更新版本日志 feat(editor): 优化 neovim 和 nvchad 安装脚本,添加性能优化配置和详细文档 feat: 优化Python开发环境安装脚本,分离基础包和机器学习包,修复virtualenvwrapper配置 feat: 优化开发工具安装脚本,统一使用common.sh中的函数,改进错误处理 fix: 修复DNMP安装脚本,跳过自动配置PHP开发环境 fix: 提交删除的 init.sh 和 mysql.sh 文件
463 lines
12 KiB
Bash
463 lines
12 KiB
Bash
#!/bin/bash
|
|
|
|
# 设置严格模式
|
|
set -euo pipefail
|
|
|
|
# 颜色定义
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# 初始化日志
|
|
setup_logging() {
|
|
local log_dir="${USER_HOME}/.logs"
|
|
local log_file="${log_dir}/shell_setup_$(date +%Y%m%d_%H%M%S).log"
|
|
|
|
mkdir -p "$log_dir"
|
|
exec 1> >(tee -a "$log_file")
|
|
exec 2> >(tee -a "$log_file" >&2)
|
|
|
|
log_info "开始记录日志到: $log_file"
|
|
}
|
|
|
|
# 日志函数
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1" | tee -a "${LOG_FILE:-/dev/null}"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "${LOG_FILE:-/dev/null}"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" | tee -a "${LOG_FILE:-/dev/null}"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${BLUE}[SUCCESS]${NC} $1" | tee -a "${LOG_FILE:-/dev/null}"
|
|
}
|
|
|
|
# 进度显示函数
|
|
show_progress() {
|
|
local msg="$1"
|
|
echo -ne "\r${GREEN}[INFO]${NC} $msg... "
|
|
}
|
|
|
|
update_progress() {
|
|
local msg="$1"
|
|
local status="$2"
|
|
echo -e "\r${GREEN}[INFO]${NC} $msg... ${status}"
|
|
}
|
|
|
|
# 清理函数
|
|
cleanup() {
|
|
local exit_code=$?
|
|
if [ $exit_code -ne 0 ]; then
|
|
log_error "脚本执行失败,退出码: $exit_code"
|
|
if [ -n "${LAST_BACKUP:-}" ] && [ -f "$LAST_BACKUP" ]; then
|
|
log_info "正在恢复配置文件..."
|
|
manage_config "restore" "$LAST_BACKUP"
|
|
fi
|
|
fi
|
|
exit $exit_code
|
|
}
|
|
|
|
# 网络检查函数
|
|
check_network() {
|
|
local test_urls=("github.com" "raw.githubusercontent.com")
|
|
for url in "${test_urls[@]}"; do
|
|
if ! ping -c 1 $url >/dev/null 2>&1; then
|
|
log_warn "无法访问 $url"
|
|
return 1
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
# 获取镜像源
|
|
get_mirror_url() {
|
|
local original_url="$1"
|
|
local url_type="$2"
|
|
|
|
if check_network; then
|
|
echo "$original_url"
|
|
return 0
|
|
fi
|
|
|
|
case "$url_type" in
|
|
"github")
|
|
echo "https://ghproxy.com/$original_url"
|
|
;;
|
|
"omz")
|
|
echo "https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh"
|
|
;;
|
|
*)
|
|
echo "$original_url"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 配置文件管理
|
|
manage_config() {
|
|
local action="$1"
|
|
local config_file="$2"
|
|
local backup_dir="${USER_HOME}/.config_backups"
|
|
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
|
|
mkdir -p "$backup_dir"
|
|
|
|
case "$action" in
|
|
"backup")
|
|
if [ -f "$config_file" ]; then
|
|
local backup_file="${backup_dir}/$(basename ${config_file}).${timestamp}.bak"
|
|
cp "$config_file" "$backup_file"
|
|
LAST_BACKUP="$backup_file"
|
|
log_info "已备份 $config_file 到 $backup_file"
|
|
fi
|
|
;;
|
|
"restore")
|
|
local source_file="$config_file"
|
|
if [ -f "$source_file" ]; then
|
|
local dest_file=$(echo "$source_file" | sed 's/\.[0-9]*_[0-9]*\.bak$//')
|
|
cp "$source_file" "$dest_file"
|
|
log_info "已恢复 $dest_file"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 插件管理
|
|
manage_plugin() {
|
|
local plugin_name="$1"
|
|
local plugin_dir="$2"
|
|
local plugin_url="$3"
|
|
|
|
show_progress "处理插件 $plugin_name"
|
|
|
|
if [ -d "$plugin_dir" ]; then
|
|
if [ -d "$plugin_dir/.git" ]; then
|
|
(cd "$plugin_dir" && git pull origin master >/dev/null 2>&1)
|
|
if [ $? -eq 0 ]; then
|
|
update_progress "处理插件 $plugin_name" "更新成功"
|
|
return 0
|
|
fi
|
|
fi
|
|
log_warn "更新 $plugin_name 失败,尝试重新安装"
|
|
rm -rf "$plugin_dir"
|
|
fi
|
|
|
|
if git clone --depth=1 "$plugin_url" "$plugin_dir" >/dev/null 2>&1; then
|
|
update_progress "处理插件 $plugin_name" "安装成功"
|
|
return 0
|
|
else
|
|
update_progress "处理插件 $plugin_name" "安装失败"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 依赖检查
|
|
check_dependencies() {
|
|
local deps=("$@")
|
|
local missing_deps=()
|
|
|
|
show_progress "检查依赖"
|
|
|
|
for dep in "${deps[@]}"; do
|
|
if ! command -v "$dep" >/dev/null 2>&1; then
|
|
missing_deps+=("$dep")
|
|
fi
|
|
done
|
|
|
|
if [ ${#missing_deps[@]} -ne 0 ]; then
|
|
update_progress "检查依赖" "发现缺失"
|
|
log_warn "缺少以下依赖: ${missing_deps[*]}"
|
|
log_info "正在安装缺失的依赖..."
|
|
apt-get update >/dev/null 2>&1
|
|
apt-get install -y "${missing_deps[@]}" >/dev/null 2>&1
|
|
update_progress "安装依赖" "完成"
|
|
else
|
|
update_progress "检查依赖" "已满足"
|
|
fi
|
|
}
|
|
|
|
# 用户交互
|
|
confirm() {
|
|
local msg="$1"
|
|
local default="${2:-Y}"
|
|
|
|
while true; do
|
|
read -p "$msg [Y/n] " response
|
|
response=${response:-$default}
|
|
case $response in
|
|
[Yy]) return 0 ;;
|
|
[Nn]) return 1 ;;
|
|
*) echo "请输入 y 或 n" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# 检查命令是否存在
|
|
check_command() {
|
|
if ! command -v "$1" &> /dev/null; then
|
|
log_error "命令 '$1' 未找到"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# 检查是否为root用户
|
|
check_root() {
|
|
if [ "$EUID" -ne 0 ]; then
|
|
log_error "请使用root权限运行此脚本"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 获取当前用户信息
|
|
get_user_info() {
|
|
CURRENT_USER=$SUDO_USER
|
|
if [ -z "$CURRENT_USER" ]; then
|
|
CURRENT_USER=$(who | awk '{print $1}' | head -n1)
|
|
fi
|
|
USER_HOME="/home/$CURRENT_USER"
|
|
|
|
# 验证用户信息
|
|
if ! id "$CURRENT_USER" >/dev/null 2>&1; then
|
|
log_error "无法获取用户信息: $CURRENT_USER"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$USER_HOME" ]; then
|
|
log_error "用户主目录不存在: $USER_HOME"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 检查Node.js版本
|
|
check_nodejs() {
|
|
if command -v node &> /dev/null; then
|
|
local node_version=$(node -v | cut -d 'v' -f2)
|
|
if [ "$(echo -e "${node_version}\n16.0.0" | sort -V | head -n1)" = "16.0.0" ]; then
|
|
log_info "检测到 Node.js ${node_version},版本满足要求"
|
|
return 0
|
|
else
|
|
log_warn "当前 Node.js 版本 ${node_version} 过低,需要更新"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warn "未检测到 Node.js"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 安装Node.js
|
|
install_nodejs() {
|
|
if ! check_nodejs; then
|
|
log_info "安装 Node.js..."
|
|
# 尝试使用 Debian 仓库安装
|
|
if apt-get install -y nodejs npm && check_nodejs; then
|
|
log_info "成功从 Debian 仓库安装 Node.js"
|
|
else
|
|
log_warn "从 Debian 仓库安装 Node.js 失败,尝试使用 NodeSource..."
|
|
# 删除可能存在的旧版本
|
|
apt-get remove -y nodejs npm
|
|
|
|
# 添加 NodeSource 仓库
|
|
mkdir -p /etc/apt/keyrings
|
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
|
apt-get update
|
|
apt-get install -y nodejs
|
|
fi
|
|
|
|
# 安装neovim包
|
|
su - $CURRENT_USER -c "npm install -g neovim"
|
|
fi
|
|
}
|
|
|
|
# 设置Python虚拟环境
|
|
setup_python_venv() {
|
|
local nvim_venv="${USER_HOME}/.local/share/nvim/venv"
|
|
|
|
# 创建虚拟环境
|
|
su - $CURRENT_USER -c "python3 -m venv ${nvim_venv}"
|
|
|
|
# 安装pynvim
|
|
su - $CURRENT_USER -c "${nvim_venv}/bin/pip install pynvim"
|
|
|
|
echo "${nvim_venv}"
|
|
}
|
|
|
|
# 下载文件的通用函数
|
|
download_file() {
|
|
local url="$1"
|
|
local output="$2"
|
|
local max_retries=3
|
|
local retry=0
|
|
local success=false
|
|
|
|
show_progress "下载 $(basename "$output")"
|
|
|
|
# 获取可能的镜像URL
|
|
local mirror_url=$(get_mirror_url "$url" "github")
|
|
|
|
while [ $retry -lt $max_retries ] && [ "$success" = false ]; do
|
|
if wget --no-check-certificate -q -O "$output" "$mirror_url" 2>/dev/null; then
|
|
success=true
|
|
elif curl -k -L -s -o "$output" "$mirror_url" 2>/dev/null; then
|
|
success=true
|
|
else
|
|
retry=$((retry + 1))
|
|
log_warn "下载失败,尝试重试 ($retry/$max_retries)..."
|
|
sleep 2
|
|
fi
|
|
done
|
|
|
|
if [ "$success" = false ]; then
|
|
update_progress "下载 $(basename "$output")" "失败"
|
|
return 1
|
|
fi
|
|
|
|
update_progress "下载 $(basename "$output")" "成功"
|
|
return 0
|
|
}
|
|
|
|
# 检查并安装基础依赖
|
|
install_base_dependencies() {
|
|
log_info "安装基础依赖..."
|
|
|
|
local base_deps=(
|
|
"git"
|
|
"curl"
|
|
"wget"
|
|
"unzip"
|
|
"python3-pip"
|
|
"python3-venv"
|
|
"build-essential"
|
|
"ca-certificates"
|
|
"fontconfig"
|
|
)
|
|
|
|
check_dependencies "${base_deps[@]}"
|
|
|
|
# 更新 CA 证书
|
|
update-ca-certificates >/dev/null 2>&1
|
|
}
|
|
|
|
# 安装字体
|
|
install_font() {
|
|
local font_url="$1"
|
|
local font_name="$2"
|
|
local font_dir="${USER_HOME}/.local/share/fonts"
|
|
|
|
show_progress "安装字体 $font_name"
|
|
|
|
# 创建字体目录
|
|
su - $CURRENT_USER -c "mkdir -p ${font_dir}"
|
|
|
|
# 下载并安装字体
|
|
if download_file "$font_url" "${font_dir}/${font_name}"; then
|
|
# 确保fontconfig已安装
|
|
ensure_package "fontconfig"
|
|
# 更新字体缓存
|
|
fc-cache -f -v >/dev/null 2>&1
|
|
update_progress "安装字体 $font_name" "完成"
|
|
return 0
|
|
else
|
|
update_progress "安装字体 $font_name" "失败"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 添加环境变量
|
|
add_env_var() {
|
|
local var_name="$1"
|
|
local var_value="$2"
|
|
local config_file="$3"
|
|
|
|
if ! grep -q "export ${var_name}=" "$config_file" 2>/dev/null; then
|
|
echo "export ${var_name}=\"${var_value}\"" >> "$config_file"
|
|
log_info "添加环境变量 ${var_name}=${var_value}"
|
|
fi
|
|
}
|
|
|
|
# 添加PATH
|
|
add_path() {
|
|
local new_path="$1"
|
|
local config_file="$2"
|
|
|
|
if ! grep -q "export PATH=.*${new_path}" "$config_file" 2>/dev/null; then
|
|
echo "export PATH=\"${new_path}:\$PATH\"" >> "$config_file"
|
|
log_info "添加PATH ${new_path}"
|
|
fi
|
|
}
|
|
|
|
# 添加别名
|
|
add_alias() {
|
|
local alias_name="$1"
|
|
local alias_value="$2"
|
|
local config_file="$3"
|
|
|
|
if ! grep -q "alias ${alias_name}=" "$config_file" 2>/dev/null; then
|
|
echo "alias ${alias_name}='${alias_value}'" >> "$config_file"
|
|
log_info "添加别名 ${alias_name}=${alias_value}"
|
|
fi
|
|
}
|
|
|
|
# 检查并创建目录
|
|
ensure_dir() {
|
|
local dir="$1"
|
|
local owner="${2:-}"
|
|
|
|
if [ ! -d "$dir" ]; then
|
|
mkdir -p "$dir"
|
|
if [ -n "$owner" ]; then
|
|
chown -R "$owner" "$dir"
|
|
fi
|
|
log_info "创建目录 $dir"
|
|
fi
|
|
}
|
|
|
|
# 检查并安装依赖包
|
|
ensure_package() {
|
|
local package="$1"
|
|
|
|
if ! dpkg -l | grep -q "^ii $package " 2>/dev/null; then
|
|
log_info "安装 $package..."
|
|
apt-get update >/dev/null 2>&1
|
|
apt-get install -y "$package" >/dev/null 2>&1
|
|
log_success "$package 安装完成"
|
|
fi
|
|
}
|
|
|
|
# 检查进程是否在运行
|
|
check_process() {
|
|
local process="$1"
|
|
|
|
if pgrep -x "$process" >/dev/null; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# 等待进程结束
|
|
wait_process() {
|
|
local process="$1"
|
|
local timeout="$2"
|
|
local count=0
|
|
|
|
while check_process "$process" && [ $count -lt $timeout ]; do
|
|
sleep 1
|
|
count=$((count + 1))
|
|
done
|
|
|
|
if check_process "$process"; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# 设置trap
|
|
trap cleanup EXIT |