同步操作将从 yijingsec/LinuxEnvConfig 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
#!/usr/bin/env bash## Copyright 2026 Hunan Yijing Technologies Co., Ltd## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━# 📝 模块描述 : 系统实用工具与包/服务管理# 📁 文件路径 : lib/utils.sh# 👤 作者信息 : mingy# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━# ═══════════════════════════════════════════════════════════════# 文件备份工具# ═══════════════════════════════════════════════════════════════backup_file() {local file="1ドル"local backup_suffix="${2:-.bak}"local desc="${3:-}"if [[ -f $file ]]; thenlocal backupif [[ -n $desc ]]; thenbackup="${file}${backup_suffix}.${desc}.$(date +%Y%m%d_%H%M%S)"elsebackup="${file}${backup_suffix}.$(date +%Y%m%d_%H%M%S)"ficp "$file" "$backup"msg_info "已备份: $file → $backup"return 0elsemsg_warning "文件不存在: $file"return 1fi}# ═══════════════════════════════════════════════════════════════# 系统信息查询# ═══════════════════════════════════════════════════════════════get_arch() { dpkg --print-architecture 2>/dev/null || uname -m; }get_memory_info() { LANG=C free -h 2>/dev/null | grep "^Mem:" | awk '{print 2ドル}'; }get_disk_usage() { df -h / 2>/dev/null | tail -1 | awk '{print 5ドル}'; }# ═══════════════════════════════════════════════════════════════# APT软件包管理# ═══════════════════════════════════════════════════════════════is_package_installed() { dpkg -l "1ドル" 2>/dev/null | grep -q "^ii"; }is_package_available() { apt-cache show "1ドル" >/dev/null 2>&1; }apt_update_with_progress() {msg_info "正在从软件源同步软件包索引 (请稍候)..."show_progress 5 100local status=0DEBIAN_FRONTEND=noninteractive apt-get update -o APT::Status-Fd=3 3>&1 1>/dev/null 2>/dev/null | while read -r line; doif [[ "$line" =~ ^(progress|dlstatus): ]]; thenlocal percentpercent=$(echo "$line" | cut -d: -f3 | tr -d ' ' | cut -d. -f1)if [[ "$percent" =~ ^[0-9]+$ ]]; thenshow_progress "$percent" 100fifidonestatus=${PIPESTATUS[0]}show_progress 100 100echo "" >&2return "$status"}apt_install_with_progress() {msg_info "正在下载并安装软件包..."show_progress 5 100local status=0DEBIAN_FRONTEND=noninteractive apt-get install -y -o APT::Status-Fd=3 "$@" 3>&1 1>/dev/null 2>/dev/null | while read -r line; doif [[ "$line" =~ ^(progress|status): ]]; thenlocal percentpercent=$(echo "$line" | cut -d: -f3 | tr -d ' ' | cut -d. -f1)if [[ "$percent" =~ ^[0-9]+$ ]]; thenshow_progress "$percent" 100fifidonestatus=${PIPESTATUS[0]}show_progress 100 100echo "" >&2return "$status"}install_packages() {local missing=()for pkg in "$@"; do if ! is_package_installed "$pkg"; then missing+=("$pkg"); fi; doneif [[ ${#missing[@]} -eq 0 ]]; then msg_info "所有包已安装"; return 0; fimsg_info "准备安装缺失组件: ${missing[*]}"apt_update_with_progressif apt_install_with_progress "${missing[@]}"; thenmsg_success "软件包安装成功"return 0fimsg_error "软件包安装失败"return 1}install_package() { install_packages "1ドル"; }remove_package() {if ! is_package_installed "1ドル"; then msg_info "1ドル未安装"; return 0; fimsg_info "正在卸载1ドル..."if DEBIAN_FRONTEND=noninteractive apt-get remove -y -qq "1ドル" >/dev/null 2>&1; thenmsg_success "1ドル卸载成功"return 0fimsg_error "1ドル卸载失败"return 1}# ═══════════════════════════════════════════════════════════════# Systemd服务管理# ═══════════════════════════════════════════════════════════════is_service_active() { systemctl is-active --quiet "1ドル" 2>/dev/null; }start_service() {msg_info "正在启动1ドル服务..."if systemctl start "1ドル" 2>/dev/null; thenmsg_success "已启动1ドル服务"; return 0fimsg_error "1ドル启动失败"; return 1}stop_service() {msg_info "正在停止1ドル服务..."if systemctl stop "1ドル" 2>/dev/null; thenmsg_success "已停止1ドル服务"; return 0fimsg_error "停止1ドル服务失败"; return 1}restart_service() {msg_info "正在重启1ドル服务..."if systemctl restart "1ドル" 2>/dev/null; thenmsg_success "已重启1ドル服务"; return 0fimsg_error "重启1ドル服务失败"; return 1}enable_service() { systemctl enable "1ドル" 2>/dev/null && msg_success "已启用 1ドル 开机自启"; }disable_service() { systemctl disable "1ドル" 2>/dev/null && msg_success "已取消 1ドル 开机自启"; }# ═══════════════════════════════════════════════════════════════# Docker环境检测与初始化# ═══════════════════════════════════════════════════════════════check_docker() {if ! command_exists docker; thenmsg_info "Docker未安装, 开始尝试安装..."if declare -f install_docker >/dev/null; theninstall_dockerelsemsg_error "无法找到安装Docker的函数, 请先手动安装Docker"return 1fifiif ! is_service_active docker; thenmsg_info "正在启动Docker服务..."start_service dockerfireturn 0}check_docker_compose() {if declare -p COMPOSE_CMD >/dev/null 2>&1 && [[ "$(declare -p COMPOSE_CMD)" == *"="* ]] && [[ "${COMPOSE_CMD[0]-}" =~ ^docker ]]; thenreturn 0fi# shellcheck disable=SC2034if docker compose version >/dev/null 2>&1; thenCOMPOSE_CMD=(docker compose)return 0elif command_exists docker-compose; thenCOMPOSE_CMD=(docker-compose)return 0elsemsg_info "Docker Compose未安装, 开始尝试安装..."if declare -f install_docker_compose_standalone >/dev/null; theninstall_docker_compose_standaloneCOMPOSE_CMD=(docker-compose)return 0elsemsg_error "无法找到安装Docker Compose的函数, 请先手动安装"return 1fifi}docker_pull_image() {local image="1ドル"msg_info "正在拉取镜像:${image}..."local prev_msg=""local lines_printed=0docker pull "$image" 2>&1 | stdbuf -oL grep -E "^[a-f0-9]{12}:|Digest:|Status:" | while read -r line; dolocal clean_lineclean_line=$(echo "$line" | xargs | cut -c 1-80)[[ -z "$clean_line" ]] && continuewhile [[ $lines_printed -gt 0 ]]; doprintf "0円33[A\r0円33[K"((lines_printed--))doneif [[ -n "$prev_msg" ]]; thenprintf " ${GRAY}➜ %s${NC}\n" "$prev_msg"printf " ${BRIGHT_BLUE}➜${NC}${CYAN}%s${NC}\n" "$clean_line"lines_printed=2elseprintf " ${BRIGHT_BLUE}➜${NC}${CYAN}%s${NC}\n" "$clean_line"lines_printed=1fiprev_msg="$clean_line"doneif docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "${image}"; thenreturn 0elsemsg_error "镜像${image}拉取失败"return 1fi}# ═══════════════════════════════════════════════════════════════# Docker容器生命周期管理# ═══════════════════════════════════════════════════════════════container_exists() { docker inspect "1ドル" >/dev/null 2>&1; }container_running() { [[ "$(docker inspect -f '{{.State.Running}}' "1ドル" 2>/dev/null)" == "true" ]]; }docker_image_exists() { docker image inspect "1ドル" >/dev/null 2>&1; }docker_stop_container() {local container="1ドル" label="${2:-1ドル}"if ! container_running "$container"; thenmsg_info "${label}容器当前未处于运行状态"return 0fimsg_info "正在停止容器..."docker stop "$container" >/dev/null 2>&1action "${label}服务已停止" "停止服务失败"}docker_start_container() {local container="1ドル" label="${2:-1ドル}"if ! container_exists "$container"; thenmsg_error "未找到${label}容器, 请先执行安装"return 1fimsg_info "正在启动容器服务..."docker start "$container" >/dev/null 2>&1if action "${label}服务启动成功" "启动服务失败"; thenreturn 0elsereturn 1fi}docker_remove_container_and_image() {local container="1ドル" image="2ドル" label="${3:-1ドル}"local found=falseif container_exists "$container"; thenfound=trueif confirm "检测到${label}容器, 确定要移除吗?"; thenmsg_info "正在移除容器..."docker rm -f "$container" >/dev/null 2>&1msg_success "${label}容器移除成功"fifiif docker_image_exists "$image"; thenfound=trueif confirm "检测到${label}相关Docker镜像, 是否执行清理?"; thenmsg_info "正在清理本地镜像..."docker rmi "$image" >/dev/null 2>&1 || truemsg_success "镜像清理完成"fifiif [[ "$found" == "false" ]]; thenmsg_info "未检测到${label}相关的容器或镜像, 可能已卸载"elsemsg_success "${label}卸载操作完成"fi}# ═══════════════════════════════════════════════════════════════# 文件下载工具# ═══════════════════════════════════════════════════════════════download_file() {local url="1ドル"local output="2ドル"local ua="${3:-Wget/1.21.1}"local success=falselocal filename; filename=$(basename "$output")if command_exists wget; then# 使用 PIPESTATUS 准确获取 wget 的退出状态sudo wget -q --user-agent="$ua" --show-progress --progress=bar:force "$url" -O "$output" 2>&1 | while read -r -d $'\r' line; dolocal percentpercent=$(echo "$line" | grep -oE "[0-9]+%" | tail -n 1)[[ -n "$percent" ]] && printf "\r${BRIGHT_BLUE}➜${NC}${CYAN}正在下载 [%s]: %-4s${NC}0円33[K" "$filename" "$percent"doneif [[ ${PIPESTATUS[0]} -eq 0 && -f "$output" ]]; thenprintf "\r${BRIGHT_BLUE}➜${NC}${CYAN}正在下载 [%s]: 100%%${NC}0円33[K\n" "$filename"success=truefifiif [[ "$success" == "false" ]] && command_exists curl; thenmsg_info "尝试使用 curl 备用下载..."sudo curl -# -L -A "$ua" -o "$output" "$url" 2>&1 | while read -r -d $'\r' line; dolocal percentpercent=$(echo "$line" | grep -oE "[0-9.]+(%)?" | tail -n 1)[[ -n "$percent" ]] && printf "\r${BRIGHT_BLUE}➜${NC}${CYAN}正在下载 [%s]: %-6s${NC}0円33[K" "$filename" "$percent"doneif [[ ${PIPESTATUS[0]} -eq 0 && -f "$output" ]]; thenprintf "\r${BRIGHT_BLUE}➜${NC}${CYAN}正在下载 [%s]: 100%%${NC}0円33[K\n" "$filename"success=truefifiif [[ "$success" == "true" ]]; thenreturn 0elsereturn 1fi}# ═══════════════════════════════════════════════════════════════# Docker部署交互# ═══════════════════════════════════════════════════════════════docker_wait_healthy() {local container="1ドル" label="${2:-1ドル}" timeout="${3:-30}"msg_info "正在等待服务初始化..."local count=0while [[ $count -lt $timeout ]]; doif docker ps --format '{{.Names}}' | grep -q "^${container}$"; thenmsg_success "容器处于运行状态"return 0fisleep 1((count++))donemsg_warning "${label}容器启动超时, 请稍后通过 docker ps 检查"return 1}prompt_host_ip() {local label="${1:-服务}" var_name="${2:-host_ip}" do_validate="${3:-true}"local default_ipdefault_ip=$(get_best_ip)local input_valread -r -p " ${CYAN}请输入启动${label}的主机地址 [${default_ip}]: ${NC}" input_valinput_val="${input_val:-$default_ip}"if [[ "$do_validate" == "true" ]]; thenvalidate_ip "$input_val" || return 1fiprintf -v "$var_name" "%s" "$input_val"}prompt_host_port() {local label="${1:-服务}" default_port="2ドル" var_name="${3:-host_port}"local input_valread -r -p " ${CYAN}请输入${label}映射端口 [${default_port}]: ${NC}" input_valinput_val="${input_val:-$default_port}"validate_port "$input_val" || return 1printf -v "$var_name" "%s" "$input_val"}show_access_info() {draw_line "-"local linefor line in "$@"; domsg_star "$line"donedraw_line "-"}# ═══════════════════════════════════════════════════════════════# DockerCompose生命周期管理# ═══════════════════════════════════════════════════════════════# 内部函数:实现双行静默刷新渲染_docker_compose_render_premium() {local label="1ドル"local action="2ドル"shift 2local compose_args=("$action")[[ "$action" == "up" ]] && compose_args=("up" "-d")local prev_msg=""# 忽略 Waiting 状态,捕获核心变更sudo "${COMPOSE_CMD[@]}" "${compose_args[@]}" "$@" 2>&1 | while read -r line; doif [[ "$line" =~ (Created|Started|Healthy|Running|Stopped|Removing|Removed|Pulled|Up-to-date) ]] && [[ ! "$line" =~ "Waiting" ]]; thenlocal clean_line; clean_line=$(echo "$line" | grep -oE "(Container|Network|Volume|Service|Image) [^ ]+ (Created|Started|Healthy|Running|Stopped|Removing|Removed|Pulled|Up-to-date)" | head -n 1)[[ -z "$clean_line" ]] && clean_line=$(echo "$line" | xargs | cut -c 1-60)# 双行渲染逻辑if [[ -n "$prev_msg" ]]; thenprintf "0円33[A\r0円33[K" # 回退一行并清除printf "0円33[A\r0円33[K" # 再回退一行并清除printf " ${GRAY}➜ %s${NC}\n" "$prev_msg"fiprintf " ${BRIGHT_BLUE}➜${NC}${CYAN}%s${NC}\n" "$clean_line"prev_msg="$clean_line"fidone}docker_compose_stop() {local dir="1ドル" label="${2:-服务}"if [[ ! -d "$dir" ]]; thenmsg_error "未找到${label}安装目录: $dir"return 1ficheck_docker_compose || return 1cd "$dir" || return 1msg_info "正在停止${label}容器服务..."_docker_compose_render_premium "${label}" "stop"action "${label}服务已停止" "${label}服务停止失败"}docker_compose_start() {local dir="1ドル" label="${2:-服务}" mode="${3:-start}"if [[ ! -d "$dir" ]]; thenmsg_error "未找到${label}安装目录, 请先执行安装"return 1ficheck_docker_compose || return 1cd "$dir" || return 1msg_info "正在启动${label}容器服务..."local action="start"[[ "$mode" == "up" ]] && action="up"_docker_compose_render_premium "${label}" "$action"action "${label}服务启动成功" "${label}服务启动失败"}docker_compose_remove() {local dir="1ドル" label="2ドル"shift 2local images=("$@")local found=falseif [[ -d "$dir" ]]; thenfound=trueif confirm "检测到${label}安装目录, 确定要卸载吗?"; thencheck_docker_compose || return 1msg_info "正在清理容器资源..."cd "$dir" || return 1sudo "${COMPOSE_CMD[@]}" down >/dev/null 2>&1msg_info "正在清理物理文件..."cd ~ || return 1sudo rm -rf "$dir"msg_success "物理环境清理完成"fifiif [[ ${#images[@]} -gt 0 ]]; thenlocal image_exists=falsefor img in "${images[@]}"; doif docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "${img}"; thenimage_exists=truebreakfidoneif [[ "$image_exists" == "true" ]]; thenfound=trueif confirm "检测到${label}相关Docker镜像, 是否执行清理?"; thenmsg_info "正在清理本地镜像..."for img in "${images[@]}"; dodocker rmi "${img}" >/dev/null 2>&1 || truedonemsg_success "镜像清理完成"fififiif [[ "$found" == "false" ]]; thenmsg_info "未检测到${label}相关的环境资源, 可能已卸载"elsemsg_success "${label}卸载操作完成"fi}# ═══════════════════════════════════════════════════════════════# 通用备份管理# ═══════════════════════════════════════════════════════════════backup_list() {ls -t "${1}".bak* 2>/dev/null}backup_display() {local label="1ドル"shiftlocal backups=("$@")if [[ ${#backups[@]} -eq 0 ]]; thenmsg_warning "目前没有任何${label}备份文件"return 1fiecho ""local widths="6 32 12 18"msg_table_row "$widths" "${BOLD}编号" "备份文件名" "标签" "修改时间${NC}"draw_line "-"local ifor i in "${!backups[@]}"; dolocal file="${backups[$i]}"local filenamefilename=$(basename "$file")local tag="legacy"if [[ $filename =~ \.bak\.([^.]+)\. ]]; thentag="${BASH_REMATCH[1]}"filocal mtimemtime=$(date -r "$file" "+%Y-%m-%d %H:%M")msg_table_row "$widths" "$((i+1))" "$filename" "$tag" "$mtime"doneecho ""return 0}backup_preview() {local file="1ドル" pattern="${2:-.*}"local totaltotal=$(grep -c "$pattern" "$file" 2>/dev/null || echo 0)msg_info "备份内容预览($file):"draw_line "-" "${GRAY}"grep "$pattern" "$file" | head -n 5 | sed 's/^/ /'if [[ $total -gt 5 ]]; thenecho " ... (共${total}条记录)"fidraw_line "-" "${GRAY}"}backup_select() {local title="1ドル" target_file="2ドル" label="3ドル"show_section "$title"local backups=()mapfile -t backups < <(backup_list "$target_file")if ! backup_display "$label" "${backups[@]}"; then return 1; filocal choicemsg_prompt "请输入备份编号 [1-${#backups[@]}, 0取消]"if [[ $choice == "0" ]]; thenreturn 1fiif ! [[ $choice =~ ^[0-9]+$ ]] || [[ $choice -lt 1 || $choice -gt ${#backups[@]} ]]; thenmsg_error "无效编号"return 1fi_SELECTED_BACKUP="${backups[$((choice-1))]}"return 0}backup_clear_all() {local label="1ドル" target_file="2ドル"show_section "清空全部${label}备份"local backups=()mapfile -t backups < <(backup_list "$target_file")if [[ ${#backups[@]} -eq 0 ]]; thenmsg_warning "没有备份文件可清理"return 0fimsg_warning "即将删除 ${#backups[@]} 个备份文件"if confirm "你确定要清空所有的${label}历史备份吗?"; thenif confirm "请再次确认(操作不可恢复):"; thenrm -f "${target_file}".bak*msg_success "所有备份清理完毕"fifi}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。