Mars 6ddba3e722 Initial commit
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 文件
2025-03-25 16:14:03 +08:00

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