From a1edcd3971f27ab39b76104c4b25bd62c09d57f7 Mon Sep 17 00:00:00 2001 From: fanhuadesenlinnn Date: Mon, 24 Nov 2025 09:35:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20system=5Fmanager.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- system_manager.sh | 1525 ++++++++++++--------------------------------- 1 file changed, 392 insertions(+), 1133 deletions(-) diff --git a/system_manager.sh b/system_manager.sh index 61d89c7..a9dc47d 100644 --- a/system_manager.sh +++ b/system_manager.sh @@ -1,113 +1,70 @@ #!/usr/bin/env bash -# Linux 系统管理工具 -# 功能: Docker安装管理 + 交换文件管理 + 常用工具管理 -# 版本: 1.0.2 +# ============================================================================== +# Linux 全能系统管理工具 (Pro版) +# 支持: Debian系 / RHEL系(含麒麟,龙蜥,欧拉) / Arch / Alpine / OpenSUSE +# 功能: Docker管理 | Swap管理 | 工具箱 | 系统源优化 +# 版本: 2.0.0 +# ============================================================================== +# ---------- 1. 全局配置与初始化 ---------- -# ========== 全局配置 ========== -readonly SCRIPT_NAME="$(basename "$0")" +# 严格模式 (部分开启,避免过早退出) +set -o pipefail + +# 路径配置 readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -readonly LOG_FILE="${SCRIPT_DIR}/system_manager.log" -readonly SWAPFILE="/swapfile" -readonly DAEMON_JSON="/etc/docker/daemon.json" -readonly TMUX_CONFIG="$HOME/.tmux.conf" -readonly VIM_CONFIG="$HOME/.vimrc" +readonly LOG_FILE="${SCRIPT_DIR}/sys_manager.log" +readonly SWAP_FILE="/swapfile" +readonly DOCKER_CONFIG="/etc/docker/daemon.json" # 颜色定义 -readonly RED='\033[0;31m' -readonly GREEN='\033[0;32m' -readonly YELLOW='\033[1;33m' -readonly BLUE='\033[0;34m' -readonly CYAN='\033[0;36m' -readonly NC='\033[0m' +readonly C_RED='\033[0;31m' +readonly C_GREEN='\033[0;32m' +readonly C_YELLOW='\033[1;33m' +readonly C_BLUE='\033[0;34m' +readonly C_CYAN='\033[0;36m' +readonly C_RESET='\033[0m' + +# 全局变量 (由 sys_detect 填充) +G_OS_DISTRO="" # 发行版 ID (ubuntu, centos, alpine, kylin...) +G_PKG_MANAGER="" # 包管理器 (apt, dnf, yum, apk, pacman...) +G_SVC_MANAGER="" # 服务管理器 (systemd, openrc, service) +G_IS_CHINA="" # 是否在中国大陆 + +# ---------- 2. 基础工具函数 ---------- -# ========== 核心工具函数 ========== log() { - echo -e "${GREEN}[$(date +'%H:%M:%S')]${NC} $*" | tee -a "$LOG_FILE" -} - -log_info() { - echo -e "${BLUE}[信息]${NC} $*" | tee -a "$LOG_FILE" -} - -log_warn() { - echo -e "${YELLOW}[警告]${NC} $*" | tee -a "$LOG_FILE" -} - -log_error() { - echo -e "${RED}[错误]${NC} $*" | tee -a "$LOG_FILE" >&2 -} - -log_success() { - echo -e "${GREEN}[成功]${NC} $*" | tee -a "$LOG_FILE" -} - -# 系统检查 -check_root() { - if [[ $EUID -ne 0 ]]; then - log_error "需要root权限运行此脚本" - log_info "请使用: sudo $SCRIPT_NAME" - exit 1 - fi -} - -check_virtualization() { - if [[ -d "/proc/vz" && ! -d "/proc/bc" ]]; then - log_error "OpenVZ容器不支持此功能" - return 1 - fi - return 0 -} - -detect_region() { - local country - country=$(curl -s --connect-timeout 3 --max-time 5 ipinfo.io/country 2>/dev/null || echo "") - [[ "$country" == "CN" ]] -} - -# 包管理器检测 -detect_package_manager() { - if command -v apt &>/dev/null; then - echo "apt" - elif command -v yum &>/dev/null; then - echo "yum" - elif command -v dnf &>/dev/null; then - echo "dnf" - elif command -v pacman &>/dev/null; then - echo "pacman" - elif command -v zypper &>/dev/null; then - echo "zypper" - else - echo "unknown" - fi -} - -# 发行版检测 -detect_distro() { - if [[ -f /etc/os-release ]]; then - . /etc/os-release - echo "${ID,,}" - elif [[ -f /etc/redhat-release ]]; then - echo "rhel" - elif [[ -f /etc/debian_version ]]; then - echo "debian" - else - echo "unknown" - fi -} - -# 用户交互 -confirm() { - local prompt="${1:-确认操作}" - local default="${2:-N}" - - echo -n "$prompt [y/N]: " - read -r choice - case "${choice:-$default}" in - [yY]|[yY][eE][sS]) return 0 ;; - *) return 1 ;; + local level="$1" + local msg="$2" + local color="" + case "$level" in + INFO) color="$C_BLUE" ;; + SUCCESS) color="$C_GREEN" ;; + WARN) color="$C_YELLOW" ;; + ERROR) color="$C_RED" ;; esac + echo -e "${color}[$(date +'%H:%M:%S')][${level}]${C_RESET} ${msg}" | tee -a "$LOG_FILE" +} + +# 装饰性标题 +print_banner() { + local title="$1" + echo + echo -e "${C_CYAN}══════════════════════════════════════════════════════${C_RESET}" + echo -e "${C_CYAN} $title${C_RESET}" + echo -e "${C_CYAN}══════════════════════════════════════════════════════${C_RESET}" + echo +} + +# 交互确认 +confirm() { + local prompt="${1:-确认执行此操作?}" + local default="${2:-n}" # 默认 no + local choice + echo -ne "${C_YELLOW}${prompt} [y/N]: ${C_RESET}" + read -r choice + [[ "$choice" =~ ^[Yy]$ ]] } pause() { @@ -116,1108 +73,410 @@ pause() { echo } -# ========== Docker 管理模块 ========== -docker_check_installed() { - command -v docker &>/dev/null -} +# ---------- 3. 系统抽象层 (核心) ---------- -docker_get_version() { - docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -n1 -} +sys_detect() { + # 1. 权限检查 + [[ $EUID -ne 0 ]] && { log ERROR "请使用 sudo 或 root 运行此脚本"; exit 1; } -docker_create_daemon_config() { - local mirror_url="${1:-}" - - mkdir -p "$(dirname "$DAEMON_JSON")" - [[ -f "$DAEMON_JSON" ]] && cp "$DAEMON_JSON" "${DAEMON_JSON}.bak.$(date +%Y%m%d-%H%M-%S)" - - if [[ -n "$mirror_url" ]]; then - cat > "$DAEMON_JSON" << EOF -{ - "registry-mirrors": ["$mirror_url"], - "log-driver": "json-file", - "log-opts": { - "max-size": "100m", - "max-file": "3" - }, - "storage-driver": "overlay2", - "exec-opts": ["native.cgroupdriver=systemd"] -} -EOF + # 2. 发行版检测 + if [[ -f /etc/os-release ]]; then + source /etc/os-release + G_OS_DISTRO="${ID,,}" else - cat > "$DAEMON_JSON" << EOF + G_OS_DISTRO="unknown" + fi + + # 3. 包管理器检测 + if command -v dnf &>/dev/null; then G_PKG_MANAGER="dnf" + elif command -v apt &>/dev/null; then G_PKG_MANAGER="apt" + elif command -v yum &>/dev/null; then G_PKG_MANAGER="yum" + elif command -v apk &>/dev/null; then G_PKG_MANAGER="apk" + elif command -v pacman &>/dev/null; then G_PKG_MANAGER="pacman" + elif command -v zypper &>/dev/null; then G_PKG_MANAGER="zypper" + else + log ERROR "无法识别包管理器,不支持此系统" + exit 1 + fi + + # 4. 服务管理器检测 + if command -v systemctl &>/dev/null; then G_SVC_MANAGER="systemd" + elif command -v rc-service &>/dev/null; then G_SVC_MANAGER="openrc" # Alpine + else G_SVC_MANAGER="service"; fi + + # 5. 区域检测 (用于镜像源优化) + if curl -s --connect-timeout 2 -m 2 https://www.google.com &>/dev/null; then + G_IS_CHINA="false" + else + G_IS_CHINA="true" + fi +} + +# 统一包安装接口 +sys_install_pkg() { + local pkgs=("$@") + [[ ${#pkgs[@]} -eq 0 ]] && return 0 + + log INFO "正在安装: ${pkgs[*]} (使用 $G_PKG_MANAGER)..." + case "$G_PKG_MANAGER" in + apt) apt-get update -qq && apt-get install -y "${pkgs[@]}" ;; + dnf|yum) $G_PKG_MANAGER install -y "${pkgs[@]}" ;; + apk) apk add --no-cache "${pkgs[@]}" ;; + pacman) pacman -Sy --noconfirm "${pkgs[@]}" ;; + zypper) zypper install -y --no-confirm "${pkgs[@]}" ;; + esac +} + +# 统一包卸载接口 +sys_remove_pkg() { + local pkgs=("$@") + [[ ${#pkgs[@]} -eq 0 ]] && return 0 + + log INFO "正在卸载: ${pkgs[*]}..." + case "$G_PKG_MANAGER" in + apt) apt-get purge -y "${pkgs[@]}" && apt-get autoremove -y ;; + dnf|yum) $G_PKG_MANAGER remove -y "${pkgs[@]}" ;; + apk) apk del "${pkgs[@]}" ;; + pacman) pacman -Rs --noconfirm "${pkgs[@]}" ;; + zypper) zypper remove -y "${pkgs[@]}" ;; + esac +} + +# 统一服务管理接口 +sys_service() { + local action="$1" # start, stop, restart, enable + local service="$2" + + [[ -z "$service" ]] && return 1 + + case "$G_SVC_MANAGER" in + systemd) + [[ "$action" == "enable" ]] && systemctl enable --now "$service" || systemctl "$action" "$service" + ;; + openrc) + [[ "$action" == "enable" ]] && rc-update add "$service" default && rc-service "$service" start || rc-service "$service" "$action" + ;; + *) + service "$service" "$action" + ;; + esac +} + +# ---------- 4. Docker 模块 ---------- + +mod_docker_menu() { + while true; do + clear + print_banner "Docker 容器管理" + local status="未安装" + command -v docker &>/dev/null && status="${C_GREEN}已安装 $(docker --version | awk '{print $3}' | tr -d ,)${C_RESET}" + + echo -e "当前状态: $status" + echo "1. 安装 Docker (自动适配源)" + echo "2. 配置国内镜像加速 (智能检测)" + echo "3. 卸载 Docker" + echo "0. 返回上级" + echo + read -rp "请选择: " opt + case $opt in + 1) mod_docker_install ;; + 2) mod_docker_mirror ;; + 3) mod_docker_remove ;; + 0) return ;; + *) log ERROR "无效选择" ;; + esac + pause + done +} + +mod_docker_install() { + if command -v docker &>/dev/null; then + log WARN "Docker 已安装" + return + fi + + log INFO "开始安装 Docker..." + + # 特殊处理 Alpine + if [[ "$G_OS_DISTRO" == "alpine" ]]; then + sys_install_pkg docker docker-compose + sys_service enable docker + log SUCCESS "Docker 安装完成 (Alpine)" + return + fi + + # 通用官方脚本安装 (兼容性最好) + local install_cmd="curl -fsSL https://get.docker.com | bash" + if [[ "$G_IS_CHINA" == "true" ]]; then + install_cmd="curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun" + fi + + eval "$install_cmd" + + if command -v docker &>/dev/null; then + sys_service enable docker + log SUCCESS "Docker 安装成功" + else + log ERROR "Docker 安装失败,请检查网络或系统日志" + fi +} + +mod_docker_mirror() { + if ! command -v docker &>/dev/null; then log ERROR "请先安装 Docker"; return; fi + [[ "$G_IS_CHINA" != "true" ]] && { log INFO "检测到非中国大陆IP,通常无需配置加速"; confirm "仍要配置吗?" || return; } + + local mirrors='["https://docker.1panel.live", "https://docker.m.daocloud.io"]' + + mkdir -p "$(dirname "$DOCKER_CONFIG")" + cat > "$DOCKER_CONFIG" </dev/null; then - mirror_url="$mirror" - break - fi - done - - [[ -f "$DAEMON_JSON" ]] && cp "$DAEMON_JSON" "${DAEMON_JSON}.bak.$(date +%Y%m%d-%H%M-%S)" - - docker_create_daemon_config "$mirror_url" - echo $mirror_url - - systemctl daemon-reload - systemctl restart docker - - log_success "Docker镜像加速器配置完成: $mirror_url" -} - -docker_download_script() { - local urls=( - "https://get.docker.com" - "https://cdn.jsdelivr.net/gh/docker/docker-install@master/install.sh" - "https://testingcf.jsdelivr.net/gh/docker/docker-install@master/install.sh" - "https://fastly.jsdelivr.net/gh/docker/docker-install@master/install.sh" - "https://gcore.jsdelivr.net/gh/docker/docker-install@master/install.sh" - "https://raw.githubusercontent.com/docker/docker-install/master/install.sh" - ) - - for url in "${urls[@]}"; do - log_info "尝试下载: $url" - if curl -fsSL --connect-timeout 10 --max-time 30 "$url" -o get-docker.sh; then - log_success "下载成功" - return 0 - fi - log_warn "下载失败: $url" - done - - return 1 -} - -docker_install() { - log_info "开始安装Docker..." - - if detect_region; then - export DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce" - log_info "使用国内镜像源" - fi - - if ! docker_download_script; then - log_error "无法下载Docker安装脚本" - return 1 - fi - - bash get-docker.sh 2>&1 | tee -a "$LOG_FILE" - rm -f get-docker.sh - - systemctl enable --now docker - - docker --version | tee -a "$LOG_FILE" - log_success "Docker安装完成" -} - -docker_uninstall() { - if ! confirm "确认卸载Docker及所有数据"; then - return 0 - fi - - log_info "停止Docker服务..." - systemctl stop docker.socket docker.service containerd.service 2>/dev/null || true - systemctl stop docker 2>/dev/null || true - - log_info "卸载Docker软件包..." - if command -v apt &>/dev/null; then - apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - apt autoremove -y - elif command -v yum &>/dev/null; then - yum remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - elif command -v dnf &>/dev/null; then - dnf remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - fi - - log_info "清理Docker数据..." rm -rf /var/lib/docker /var/lib/containerd /etc/docker - - log_success "Docker卸载完成" + log SUCCESS "Docker 已卸载并清理数据" } -docker_menu() { +# ---------- 5. Swap 交换分区模块 ---------- + +mod_swap_menu() { while true; do clear - cat << 'EOF' -╔══════════════════════════════════════╗ -║ Docker 管理 ║ -╚══════════════════════════════════════╝ -EOF + print_banner "Swap 虚拟内存管理" - if docker_check_installed; then - local version - version=$(docker_get_version) - echo -e "${GREEN}状态: 已安装 (v${version})${NC}" - cat << 'EOF' + # 获取当前 Swap 信息 + local current_swap + current_swap=$(free -m | awk '/Swap/ {print $2}') + local swap_status="${C_RED}未启用${C_RESET}" + [[ "$current_swap" -gt 0 ]] && swap_status="${C_GREEN}已启用 (${current_swap} MB)${C_RESET}" -1. 配置镜像加速器 -2. 重启Docker服务 -3. 卸载Docker -0. 返回主菜单 -EOF - else - echo -e "${RED}状态: 未安装${NC}" - cat << 'EOF' - -1. 安装Docker -0. 返回主菜单 -EOF - fi - + echo -e "当前状态: $swap_status" + echo "1. 创建/扩容 Swap" + echo "2. 删除 Swap" + echo "0. 返回上级" echo - read -rp "请选择操作: " choice - - case "$choice" in - 1) - if docker_check_installed; then - docker_configure_mirror - else - docker_install && docker_configure_mirror - fi - pause - ;; - 2) - if docker_check_installed; then - systemctl restart docker && log_success "Docker服务重启完成" - pause - else - continue - fi - ;; - 3) - if docker_check_installed; then - docker_uninstall - pause - else - continue - fi - ;; - 0) - return 0 - ;; - *) - log_error "无效选择" - sleep 1 - ;; + read -rp "请选择: " opt + case $opt in + 1) mod_swap_create ;; + 2) mod_swap_remove ;; + 0) return ;; esac + pause done } -# ========== 交换文件管理模块 ========== -swap_exists() { - [[ -f "$SWAPFILE" ]] && grep -q "$SWAPFILE" /etc/fstab 2>/dev/null +mod_swap_create() { + # 检查虚拟化限制 + if [[ -d "/proc/vz" && ! -d "/proc/bc" ]]; then + log ERROR "OpenVZ 架构通常不支持 Swap" + return + fi + + if grep -q "$SWAP_FILE" /etc/fstab; then + log WARN "发现已存在的 Swap 配置,请先删除" + return + fi + + local mem_total + mem_total=$(free -m | awk '/Mem:/ {print $2}') + local recommend=$((mem_total < 2048 ? mem_total * 2 : mem_total)) + + echo -e "物理内存: ${mem_total} MB" + read -rp "请输入 Swap 大小 (MB) [默认 ${recommend}]: " size + size=${size:-$recommend} + + log INFO "正在创建 ${size} MB 的 Swap 文件..." + + # 兼容性创建 (dd 比 fallocate 兼容性好,尤其是 btrfs/xfs 某些情况) + dd if=/dev/zero of="$SWAP_FILE" bs=1M count="$size" status=progress + + chmod 600 "$SWAP_FILE" + mkswap "$SWAP_FILE" + swapon "$SWAP_FILE" + + # 持久化 + echo "$SWAP_FILE none swap sw 0 0" >> /etc/fstab + log SUCCESS "Swap 创建成功" } -swap_get_info() { - if [[ -f "$SWAPFILE" ]]; then - local size_kb - size_kb=$(stat -c%s "$SWAPFILE" 2>/dev/null) - if [[ -n "$size_kb" && "$size_kb" -gt 0 ]]; then - printf "%.0fMB" $((size_kb / 1024 / 1024)) - else - echo "未知大小" - fi - elif swapon --show 2>/dev/null | grep -q "$SWAPFILE"; then - # 如果文件不存在但swap仍激活,从swap信息获取 - awk -v swapfile="$SWAPFILE" '$1 == swapfile {printf "%.0fMB", $3/1024}' /proc/swaps - else - echo "0MB" - fi +mod_swap_remove() { + confirm "确定删除 Swap 吗?" || return + swapoff "$SWAP_FILE" 2>/dev/null + sed -i "\|$SWAP_FILE|d" /etc/fstab + rm -f "$SWAP_FILE" + log SUCCESS "Swap 已移除" } -swap_validate_size() { - local size="$1" - - if ! [[ "$size" =~ ^[0-9]+$ ]] || [[ "$size" -lt 64 ]]; then - log_error "大小必须是数字且 >= 64MB" - return 1 - fi - - local available - available=$(df / | awk 'NR==2 {print int($4/1024)}') - - if [[ "$size" -gt "$available" ]]; then - log_error "磁盘空间不足 (可用: ${available}MB)" - return 1 - fi - - return 0 -} +# ---------- 6. 常用工具模块 ---------- -swap_create() { - if swap_exists; then - log_error "交换文件已存在,请先删除" - return 1 - fi - - local memory_mb - memory_mb=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) - local suggested_size=$((memory_mb < 2048 ? memory_mb * 2 : memory_mb)) - - echo - log_info "系统内存: ${memory_mb}MB" - log_info "建议大小: ${suggested_size}MB" - echo - read -rp "请输入交换文件大小(MB) [${suggested_size}]: " swap_size - swap_size=${swap_size:-$suggested_size} - - if ! swap_validate_size "$swap_size"; then - return 1 - fi - - log_info "创建 ${swap_size}MB 交换文件..." - - if command -v fallocate &>/dev/null; then - fallocate -l "${swap_size}M" "$SWAPFILE" - else - dd if=/dev/zero of="$SWAPFILE" bs=1M count="$swap_size" status=progress - fi - - chmod 600 "$SWAPFILE" - mkswap "$SWAPFILE" - swapon "$SWAPFILE" - echo "$SWAPFILE none swap sw 0 0" >> /etc/fstab - - log_success "交换文件创建完成" -} - -swap_remove() { - if ! swap_exists; then - log_warn "交换文件不存在" - return 0 - fi - - if ! confirm "确认删除交换文件"; then - return 0 - fi - - log_info "删除交换文件..." - - swapoff "$SWAPFILE" 2>/dev/null || true - sed -i "\|$SWAPFILE|d" /etc/fstab 2>/dev/null || true - rm -f "$SWAPFILE" - - sync - echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true - - log_success "交换文件删除完成" -} - -swap_show_info() { - echo - log_info "交换文件状态:" - if [[ -f "$SWAPFILE" ]]; then - local file_size_mb - file_size_mb=$(stat -c%s "$SWAPFILE" 2>/dev/null | awk '{printf "%.0f", $1/1024/1024}') - echo -e " 文件: ${GREEN}$SWAPFILE${NC}" - echo -e " 大小: ${GREEN}${file_size_mb}MB${NC}" - - if swapon --show 2>/dev/null | grep -q "$SWAPFILE"; then - local used_kb - used_kb=$(awk -v swapfile="$SWAPFILE" '$1 == swapfile {print $4}' /proc/swaps) - local used_mb=$((used_kb / 1024)) - echo -e " 状态: ${GREEN}激活${NC} (已使用: ${used_mb}MB)" - else - echo -e " 状态: ${YELLOW}未激活${NC}" - fi - - if ! grep -q "$SWAPFILE" /etc/fstab 2>/dev/null; then - echo -e " 持久化: ${YELLOW}未配置${NC}" - else - echo -e " 持久化: ${GREEN}已配置${NC}" - fi - else - echo -e " 状态: ${RED}未创建${NC}" - fi - - echo - log_info "系统内存信息:" - free -h | grep -E "Mem|Swap" | while read -r line; do - echo " $line" - done -} - -swap_menu() { - while true; do - clear - cat << 'EOF' -╔══════════════════════════════════════╗ -║ 交换文件管理 ║ -╚══════════════════════════════════════╝ -EOF - - if swap_exists; then - echo -e "${GREEN}状态: 已配置 ($(swap_get_info))${NC}" - else - echo -e "${RED}状态: 未配置${NC}" - fi - - cat << 'EOF' - -1. 创建交换文件 -2. 删除交换文件 -3. 显示详细信息 -0. 返回主菜单 -EOF - - echo - read -rp "请选择操作: " choice - - case "$choice" in - 1) - swap_create - pause - ;; - 2) - swap_remove - pause - ;; - 3) - swap_show_info - pause - ;; - 0) - return 0 - ;; - *) - log_error "无效选择" - sleep 1 - ;; - esac - done -} - -# ========== 常用工具管理模块 ========== -# 工具包定义 -declare -A TOOL_PACKAGES=( - ["openssh"]="openssh-server openssh-client:openssh-server openssh-client:openssh:openssh" - ["nfs"]="nfs-common:nfs-common:nfs-utils:nfs-utils" - ["ping"]="iputils-ping:iputils:iputils-ping:iputils" - ["vim"]="vim:vim:vim:vim" - ["net-tools"]="net-tools:net-tools:net-tools:net-tools" - ["htop"]="htop:htop:htop:htop" - ["tmux"]="tmux:tmux:tmux:tmux" - ["curl"]="curl:curl:curl:curl" - ["wget"]="wget:wget:wget:wget" - ["git"]="git:git:git:git" +# 定义工具字典 (格式: "工具名:apt包名:yum包名:apk包名:pacman包名:zypper包名") +# 如果包名相同,后续可以省略,脚本会自动回退使用默认名 +# 例如: "vim" 在所有系统都叫 vim,除了 alpine 叫 vim (这里只是举例逻辑) +declare -A TOOLS_MAP=( + ["vim"]="vim:vim:vim:vim:vim:vim" + ["wget"]="wget" + ["curl"]="curl" + ["git"]="git" + ["htop"]="htop" + ["tmux"]="tmux" + ["zsh"]="zsh" + ["unzip"]="unzip" + ["net-tools"]="net-tools:net-tools:net-tools:net-tools:net-tools:net-tools" # ifconfig + ["ping"]="iputils-ping:iputils:iputils:iputils-ping:iputils:iputils" # ping 命令包名差异大 ) -# 获取包名 (apt:yum/dnf:pacman:zypper) -get_package_name() { - local tool="$1" - local pm="$2" - local packages="${TOOL_PACKAGES[$tool]:-}" - - [[ -z "$packages" ]] && { echo "$tool"; return; } - - case "$pm" in - apt) echo "${packages%%:*}" ;; - yum|dnf) - local pkg="${packages#*:}" - echo "${pkg%%:*}" - ;; - pacman) - local pkg="${packages#*:*:}" - echo "${pkg%%:*}" - ;; - zypper) echo "${packages##*:}" ;; - *) echo "$tool" ;; - esac -} +mod_tools_get_pkg_name() { + local tool=$1 + local def="${TOOLS_MAP[$tool]}" + local pkg_name="" -# 检查工具是否已安装 -tool_is_installed() { - local tool="$1" - case "$tool" in - openssh) - command -v sshd &>/dev/null && command -v ssh &>/dev/null - ;; - nfs) - command -v showmount &>/dev/null || systemctl list-unit-files | grep -q nfs - ;; - ping) - command -v ping &>/dev/null - ;; - net-tools) - command -v ifconfig &>/dev/null - ;; - *) - command -v "$tool" &>/dev/null - ;; + IFS=':' read -r -a parts <<< "$def" + + # 根据全局 G_PKG_MANAGER 映射索引 + # 0:通用/apt, 1:yum/dnf, 2:apk, 3:pacman, 4:zypper + local idx=0 + case "$G_PKG_MANAGER" in + apt) idx=1 ;; + yum|dnf) idx=2 ;; + apk) idx=3 ;; + pacman) idx=4 ;; + zypper) idx=5 ;; esac -} -# 安装单个工具 -install_single_tool() { - local tool="$1" - local pm="$2" - local package - - package=$(get_package_name "$tool" "$pm") - - log_info "安装 $tool (包: $package)..." - - case "$pm" in - apt) - apt update -qq - apt install -y $package - ;; - yum) - yum install -y $package - ;; - dnf) - dnf install -y $package - ;; - pacman) - pacman -S --noconfirm $package - ;; - zypper) - zypper install -y $package - ;; - *) - log_error "不支持的包管理器: $pm" - return 1 - ;; - esac - - if tool_is_installed "$tool"; then - log_success "$tool 安装完成" - - # 启动相关服务 - case "$tool" in - openssh) - systemctl restart sshd 2>/dev/null || systemctl enable --now sshd 2>/dev/null || systemctl enable --now ssh 2>/dev/null || true - ;; - esac - return 0 + # 获取对应位置的包名,如果没有则使用第一位(通用名) + if [[ -n "${parts[$idx]}" ]]; then + echo "${parts[$idx]}" else - log_error "$tool 安装失败" - return 1 + echo "${parts[0]}" # Fallback fi } -# 批量安装工具 -install_tools() { - local pm - pm=$(detect_package_manager) - - if [[ "$pm" == "unknown" ]]; then - log_error "无法识别包管理器" - return 1 - fi - - local tools=(openssh nfs ping vim net-tools htop tmux curl wget git) - local selected_tools=() - - echo - log_info "可安装的工具:" - for i in "${!tools[@]}"; do - local tool="${tools[$i]}" - local status - if tool_is_installed "$tool"; then - status="${GREEN}已安装${NC}" - else - status="${RED}未安装${NC}" - fi - printf "%2d) %-12s %b\n" $((i+1)) "$tool" "$status" - done - - echo - echo "选择安装选项:" - echo " a) 安装全部" - echo " s) 选择安装" - echo " 0) 返回" - echo - read -rp "请选择: " choice - - case "$choice" in - a|A) - selected_tools=("${tools[@]}") - ;; - s|S) - echo - read -rp "请输入工具编号(用空格分隔,如: 1 3 5): " numbers - for num in $numbers; do - if [[ "$num" =~ ^[0-9]+$ ]] && [[ "$num" -ge 1 ]] && [[ "$num" -le "${#tools[@]}" ]]; then - selected_tools+=("${tools[$((num-1))]}") - fi - done - ;; - 0) - return 0 - ;; - *) - log_error "无效选择" - return 1 - ;; - esac - - if [[ ${#selected_tools[@]} -eq 0 ]]; then - log_warn "未选择任何工具" - return 0 - fi - - log_info "将安装: ${selected_tools[*]}" - if ! confirm "确认安装这些工具"; then - return 0 - fi - - local success_count=0 - for tool in "${selected_tools[@]}"; do - if tool_is_installed "$tool"; then - log_info "$tool 已安装,跳过" - ((success_count++)) - elif install_single_tool "$tool" "$pm"; then - ((success_count++)) - fi - done - - log_info "安装完成: ${success_count}/${#selected_tools[@]} 成功" -} - -# 卸载工具 -uninstall_tool() { - local pm - pm=$(detect_package_manager) - - local tools=(openssh nfs ping vim net-tools htop tmux curl wget git) - local installed_tools=() - - for tool in "${tools[@]}"; do - if tool_is_installed "$tool"; then - installed_tools+=("$tool") - fi - done - - if [[ ${#installed_tools[@]} -eq 0 ]]; then - log_warn "没有已安装的工具" - return 0 - fi - - echo - log_info "已安装的工具:" - for i in "${!installed_tools[@]}"; do - printf "%2d) %s\n" $((i+1)) "${installed_tools[$i]}" - done - - echo - read -rp "请输入要卸载的工具编号(用空格分隔): " numbers - - local selected_tools=() - for num in $numbers; do - if [[ "$num" =~ ^[0-9]+$ ]] && [[ "$num" -ge 1 ]] && [[ "$num" -le "${#installed_tools[@]}" ]]; then - selected_tools+=("${installed_tools[$((num-1))]}") - fi - done - - if [[ ${#selected_tools[@]} -eq 0 ]]; then - log_warn "未选择任何工具" - return 0 - fi - - if ! confirm "确认卸载这些工具: ${selected_tools[*]}"; then - return 0 - fi - - for tool in "${selected_tools[@]}"; do - local package - package=$(get_package_name "$tool" "$pm") - - log_info "卸载 $tool..." - case "$pm" in - apt) apt remove -y $package ;; - yum) yum remove -y $package ;; - dnf) dnf remove -y $package ;; - pacman) pacman -R --noconfirm $package ;; - zypper) zypper remove -y $package ;; - esac - - # 停止相关服务 - case "$tool" in - openssh) - systemctl disable --now sshd 2>/dev/null || systemctl disable --now ssh 2>/dev/null || true - ;; - esac - done - - log_success "卸载完成" -} - -# Tmux配置 -configure_tmux() { - if ! tool_is_installed "tmux"; then - log_error "tmux 未安装" - return 1 - fi - - log_info "配置 tmux..." - - # 备份现有配置 - [[ -f "$TMUX_CONFIG" ]] && cp "$TMUX_CONFIG" "${TMUX_CONFIG}.bak.$(date +%Y%m%d-%H%M-%S)" - - cat > "$TMUX_CONFIG" << 'EOF' -# Tmux 配置文件 -# 修改前缀键为 Ctrl+h -set -g prefix C-h -unbind C-b -bind C-h send-prefix - -# 重新加载配置文件 -bind r source-file ~/.tmux.conf \; display-message "Config reloaded.." - -# 分割面板快捷键 -unbind '"' -bind - splitw -v # 水平分割 -unbind % -bind | splitw -h # 垂直分割 - -# 调整面板大小 -bind -r ^k resizep -U 5 # 向上调整 -bind -r ^j resizep -D 5 # 向下调整 -bind -r ^h resizep -L 5 # 向左调整 -bind -r ^l resizep -R 5 # 向右调整 - -# 面板选择 -bind k selectp -U # 上面板 -bind j selectp -D # 下面板 -bind h selectp -L # 左面板 -bind l selectp -R # 右面板 - -# 同步面板 -bind-key s setw synchronize-panes - -# 基础设置 -set -g base-index 1 # 窗口起始序号 -set -g pane-base-index 1 # 面板起始序号 -set -g status-interval 1 # 状态栏刷新间隔 -set -g status-justify left # 状态栏对齐方式 -setw -g monitor-activity on # 监控窗口活动 -set -wg window-status-format " #I #W " # 窗口状态格式 -set -wg window-status-current-format " #I:#W#F " # 当前窗口格式 -set -wg window-status-separator "" # 窗口分隔符 -set-window-option -g mode-keys vi # Vi 模式 -set -g history-limit 500000 # 历史记录限制 - -# 颜色配置 -set-option -g default-terminal "screen-256color" -set -g status-style fg=white,bg=black -setw -g window-status-style fg=cyan -setw -g window-status-current-style fg=white,bg=magenta -set -g pane-border-style fg=white -set -g pane-active-border-style fg=magenta -set -g message-style fg=colour186,bg=black,bright - -# 状态栏配置 -set -g status-left "#[fg=green]#S#[fg=yellow]-#I#[fg=cyan]-#P" -set -g status-justify centre -setw -g monitor-activity on -set -g visual-activity on -EOF - - log_success "tmux 配置完成" - log_info "配置文件: $TMUX_CONFIG" - log_info "使用 'tmux source ~/.tmux.conf' 重新加载配置" -} - -# Vim配置 -configure_vim() { - if ! tool_is_installed "vim"; then - log_error "vim 未安装" - return 1 - fi - - log_info "配置 vim..." - - # 备份现有配置 - [[ -f "$VIM_CONFIG" ]] && cp "$VIM_CONFIG" "${VIM_CONFIG}.bak.$(date +%Y%m%d-%H%M-%S)" - - cat > "$VIM_CONFIG" << 'EOF' -" Vim 基础配置 -" 显示设置 -set number " 显示行号 -set relativenumber " 相对行号 -set cursorline " 高亮当前行 -set showmatch " 显示匹配括号 -set ruler " 显示光标位置 -set laststatus=2 " 始终显示状态栏 - -" 搜索设置 -set hlsearch " 高亮搜索结果 -set incsearch " 增量搜索 -set ignorecase " 忽略大小写 -set smartcase " 智能大小写 - -" 编辑设置 -set paste -" set autoindent " 自动缩进 -" set smartindent " 智能缩进 -set expandtab " Tab转空格 -set tabstop=4 " Tab宽度 -set shiftwidth=4 " 缩进宽度 -set softtabstop=4 " 软Tab宽度 - -" 界面设置 -set wildmenu " 命令行补全菜单 -set scrolloff=5 " 滚动偏移 -set encoding=utf-8 " 编码设置 -set fileencodings=utf-8,gb2312,gb18030,gbk,ucs-bom,cp936,latin1 - -" 功能设置 -set mouse=a " 启用鼠标 -set clipboard=unnamed " 系统剪贴板 -set backspace=2 " 退格键设置 -set noswapfile " 不创建交换文件 -set nobackup " 不创建备份文件 - -" 颜色方案 -syntax on " 语法高亮 -colorscheme default " 默认配色 - -" 快捷键映射 -nnoremap h " 窗口切换 -nnoremap j -nnoremap k -nnoremap l -EOF - - log_success "vim 配置完成" - log_info "配置文件: $VIM_CONFIG" -} - -# 工具配置菜单 -configure_tools() { +mod_tools_menu() { while true; do clear - cat << 'EOF' -╔══════════════════════════════════════╗ -║ 工具配置 ║ -╚══════════════════════════════════════╝ -EOF + print_banner "常用工具箱 (适配 $G_PKG_MANAGER)" - echo - printf "1. 配置 tmux" - if [[ -f "$TMUX_CONFIG" ]]; then - echo -e " ${GREEN}(已配置)${NC}" - else - echo -e " ${RED}(未配置)${NC}" - fi - - printf "2. 配置 vim" - if [[ -f "$VIM_CONFIG" ]]; then - echo -e " ${GREEN}(已配置)${NC}" - else - echo -e " ${RED}(未配置)${NC}" - fi - - cat << 'EOF' -3. 查看配置文件 -4. 恢复默认配置 -0. 返回上级菜单 -EOF - - echo - read -rp "请选择操作: " choice - - case "$choice" in - 1) - configure_tmux - pause - ;; - 2) - configure_vim - pause - ;; - 3) - echo - echo "配置文件位置:" - echo " tmux: $TMUX_CONFIG" - echo " vim: $VIM_CONFIG" - pause - ;; - 4) - echo - echo "可恢复的配置:" - if ls "${TMUX_CONFIG}".backup.* &>/dev/null; then - echo " 1) tmux 备份文件存在" - fi - if ls "${VIM_CONFIG}".backup.* &>/dev/null; then - echo " 2) vim 备份文件存在" - fi - echo " 请手动删除配置文件或重命名备份文件" - pause - ;; - 0) - return 0 - ;; - *) - log_error "无效选择" - sleep 1 - ;; - esac - done -} - -# 工具管理主菜单 -tools_menu() { - while true; do - clear - cat << 'EOF' -╔══════════════════════════════════════╗ -║ 常用工具管理 ║ -╚══════════════════════════════════════╝ -EOF - - echo - local pm - pm=$(detect_package_manager) - echo -e "包管理器: ${GREEN}$pm${NC}" - local tools=(openssh nfs ping vim net-tools htop tmux curl wget git) - local installed_count=0 - for tool in "${tools[@]}"; do - if tool_is_installed "$tool"; then - ((installed_count++)) + local i=1 + local tool_list=() + for tool in "${!TOOLS_MAP[@]}"; do + local status="${C_RED}未装${C_RESET}" + # 简单的检查命令是否存在,部分包名和命令名不一致的需特殊处理,这里简化处理 + if command -v "$tool" &>/dev/null || ([[ "$tool" == "ping" ]] && command -v ping &>/dev/null); then + status="${C_GREEN}已装${C_RESET}" fi + printf "%2d. %-12s [%s]\n" "$i" "$tool" "$status" + tool_list+=("$tool") + ((i++)) done - echo -e "已安装工具: ${GREEN}${installed_count}${NC}/${#tools[@]}" - - cat << 'EOF' - -1. 安装工具 -2. 卸载工具 -3. 工具状态 -4. 工具配置 -0. 返回主菜单 -EOF echo - read -rp "请选择操作: " choice - - case "$choice" in - 1) - install_tools + echo " a. 一键安装所有" + echo " 0. 返回上级" + echo + read -rp "请输入序号安装 (支持多选,空格分隔,如 1 3): " choices + + case "$choices" in + 0) return ;; + a|A) + local all_pkgs=() + for t in "${tool_list[@]}"; do + all_pkgs+=("$(mod_tools_get_pkg_name "$t")") + done + sys_install_pkg "${all_pkgs[@]}" pause ;; - 2) - uninstall_tool - pause - ;; - 3) - echo - log_info "工具安装状态:" - for tool in "${tools[@]}"; do - if tool_is_installed "$tool"; then - echo -e " ${tool}: ${GREEN}已安装${NC}" - else - echo -e " ${tool}: ${RED}未安装${NC}" + *) + local install_list=() + for c in $choices; do + if [[ "$c" =~ ^[0-9]+$ ]] && [ "$c" -le "${#tool_list[@]}" ] && [ "$c" -ge 1 ]; then + local t_name="${tool_list[$((c-1))]}" + install_list+=("$(mod_tools_get_pkg_name "$t_name")") fi done + + if [[ ${#install_list[@]} -gt 0 ]]; then + sys_install_pkg "${install_list[@]}" + else + log ERROR "无效的选择" + fi pause ;; - 4) - configure_tools - ;; - 0) - return 0 - ;; - *) - log_error "无效选择" - sleep 1 - ;; esac done } +# ---------- 7. 系统源更换模块 ---------- -# ========== 系统源更换模块 ========== -set_linux_mirrors() { - log_info "开始更换系统源..." +mod_mirrors_run() { + print_banner "系统软件源更换" + log INFO "正在调用 LinuxMirrors 开源脚本..." + log WARN "此操作将替换系统源配置文件,请确保网络通畅。" - if ! command -v curl &>/dev/null; then - log_error "需要 curl 工具,请先安装" - if confirm "是否立即安装 curl"; then - local pm - pm=$(detect_package_manager) - install_single_tool "curl" "$pm" - else - return 1 + if confirm "继续执行?"; then + # 确保有 curl + if ! command -v curl &>/dev/null; then + sys_install_pkg curl fi - fi - - log_info "下载并执行系统源更换脚本..." - log_warn "注意: 此操作将修改您的系统软件源配置" - log_warn "建议在操作前备份重要数据" - - if ! confirm "确认继续执行系统源更换操作"; then - return 0 - fi - - # 执行系统源更换脚本 - if bash <(curl -sSL https://linuxmirrors.cn/main.sh); then - log_success "系统源更换完成" - log_info "建议运行系统更新命令以应用新源" - local pm - pm=$(detect_package_manager) - case "$pm" in - apt) log_info "请运行: apt update && apt upgrade -y" ;; - yum|dnf) log_info "请运行: $pm update -y" ;; - pacman) log_info "请运行: pacman -Syu" ;; - zypper) log_info "请运行: zypper refresh && zypper update -y" ;; - esac - else - log_error "系统源更换失败" - return 1 + # 调用成熟的外部脚本,避免重复造轮子 + bash <(curl -sSL https://linuxmirrors.cn/main.sh) fi } +# ---------- 8. 主程序循环 ---------- -# ========== 主程序 ========== -show_system_info() { - clear - cat << 'EOF' -╔══════════════════════════════════════╗ -║ Linux 系统管理工具 ║ -╚══════════════════════════════════════╝ -EOF +main() { + sys_detect # 系统环境初始化 - echo - log_info "系统信息:" - echo " OS: $(uname -o) $(uname -r)" - echo " 内存: $(free -h | awk '/^Mem:/ {print $2}')" - echo " 磁盘: $(df -h / | awk 'NR==2 {print $4}') 可用" - - echo - log_info "服务状态:" - printf " Docker: " - if docker_check_installed; then - echo -e "${GREEN}已安装 (v$(docker_get_version))${NC}" - else - echo -e "${RED}未安装${NC}" - fi - - printf " 交换文件: " - if swap_exists; then - echo -e "${GREEN}已配置 ($(swap_get_info))${NC}" - else - echo -e "${RED}未配置${NC}" - fi - - # 显示常用工具状态 - local tools=(vim htop tmux git) - local installed_tools=() - for tool in "${tools[@]}"; do - if tool_is_installed "$tool"; then - installed_tools+=("$tool") - fi - done - printf " 常用工具: " - if [[ ${#installed_tools[@]} -gt 0 ]]; then - echo -e "${GREEN}${installed_tools[*]}${NC}" - else - echo -e "${RED}未安装${NC}" - fi - - echo -} + # 捕获 Ctrl+C + trap 'echo -e "\n${C_YELLOW}用户取消操作,正在退出...${C_RESET}"; exit 0' SIGINT -main_menu() { while true; do - show_system_info - cat << 'EOF' -1. Docker 管理 -2. 交换文件管理 -3. 常用工具管理 -4. 更换系统源 -9. 系统信息 -10. 查看日志 -0. 退出程序 -EOF - + clear + print_banner "Linux System Manager Pro" + echo -e " 系统信息: ${C_GREEN}$G_OS_DISTRO${C_RESET} | 包管理: ${C_GREEN}$G_PKG_MANAGER${C_RESET} | 区域: $([[ "$G_IS_CHINA" == "true" ]] && echo "CN" || echo "Global")" + echo -e " 核心时间: $(uptime -p)" echo - read -rp "请选择功能模块: " choice + echo " 1. Docker 管理中心 (容器/镜像/加速)" + echo " 2. Swap 虚拟内存 (创建/扩容/删除)" + echo " 3. 常用工具箱 (Vim/Git/Tmux...)" + echo " 4. 系统源一键更换 (调用 LinuxMirrors)" + echo " 5. 查看系统日志 (查看脚本运行记录)" + echo " 0. 退出程序" + echo + read -rp " 请输入数字 [0-5]: " choice case "$choice" in - 1) - docker_menu - ;; - 2) - if check_virtualization; then - swap_menu - else - log_error "当前环境不支持交换文件功能" - pause - fi - ;; - 3) - tools_menu - ;; - 4) - set_linux_mirrors - ;; - 9) - show_system_info - pause - ;; - 10) - if [[ -f "$LOG_FILE" ]]; then - echo - echo "=== 日志文件最后20行 ===" - tail -20 "$LOG_FILE" - else - echo "暂无日志" - fi - pause - ;; + 1) mod_docker_menu ;; + 2) mod_swap_menu ;; + 3) mod_tools_menu ;; + 4) mod_mirrors_run ;; + 5) + echo + tail -n 20 "$LOG_FILE" + pause + ;; 0) - log_info "再见!" - exit 0 - ;; - *) - log_error "无效选择" - sleep 1 - ;; + log INFO "退出程序" + exit 0 + ;; + *) log ERROR "输入错误" ; sleep 1 ;; esac done } -# 程序入口 -main() { - check_root - - # 创建日志文件 - touch "$LOG_FILE" - - log "系统管理工具启动" - main_menu -} - -main +main \ No newline at end of file