简介面向 Linux 系统运维与服务器环境搭建的一键修复/安装脚本合集覆盖 Ubuntu、CentOS、Debian 等多发行版可处理系统故障修复、服务环境快速部署等常见需求。脚本内置系统检测、修复策略、安装流程与自动化配置并通过错误检测与恢复机制保障操作安全适合运维人员与 Linux 初学者降低手动操作风险、提升部署效率。资源压缩包共 24 个文件大小仅 44KB以 17 个 shell 脚本为核心涵盖系统检查、网络配置、日志清理、软件包管理以及 FileBrowser、Rust、Jupyter 等常见服务的一键安装另含 2 个 yml 编排文件、2 个 txt 说明、2 个 md 文档和 1 个 license便于查看用法、依赖与授权信息。当前已有 153 人学习下载。对于需要快速搭建或修复 Linux 环境的用户可直接按脚本模块调用省去逐条输入命令的时间说明文档和脚本注释也能帮助理解常见排错思路与自动化配置细节。1. 一键修复与安装脚本到底能帮你省多少事先看它能覆盖的三种场景服务器环境出问题时最耗时间的往往不是问题本身而是翻历史命令、对比发行版差异、改完一处又带出另一处。你多半经历过这种场面apt 源锁死、yum 依赖断裂、docker 起不来、mysql 的 lib 缺了一个版本。这时候如果手里有一份针对性的 one-click 修复与安装脚本直接跑一条命令把日志丢回去现场就能少熬两个小时。这个标题讲的就是把「各种 Linux 系统修复」和「服务器环境安装」这两类高频操作收敛成一份可检测、可分支、可重跑的 shell 脚本。它不神但能救命。适合刚接手一堆陌生服务器的新人也适合需要批量交付环境的实施工程师——你在自己的机器上试好了到现场只需要改几个变量。2. 拆开 one-click 脚本的骨架检测、分发、执行三阶段别急着写安装命令。真正能扛住生产环境的一键脚本结构上是分层的接收参数、检测系统、选择分支、执行、记日志、可回滚。很多脚本翻车不是因为最后那行 install 写错而是前面少了「这到底是个什么系统」的判断。2.1 脚本入口与参数解析怎么用一条命令接管整个环境一键脚本不等于零参数。恰恰相反为了能复用它必须把「环境差异」暴露成参数把「默认值」收敛在脚本内部。我一般会用getopts或简单的while case来解析这样调用方只需要记一个动作名比如./one-click.sh --action install --stack lnmp --version 8.2参数解析的骨架长这样#!/usr/bin/env bash set -euo pipefail ACTIONrepair STACK VERSION DRY_RUN0 while [[ $# -gt 0 ]]; do case $1 in --action) ACTION${2:-repair}; shift 2 ;; --stack) STACK${2:-}; shift 2 ;; --version) VERSION${2:-}; shift 2 ;; --dry-run) DRY_RUN1; shift ;; *) echo unknown option: $1 2; exit 2 ;; esac doneset -euo pipefail是必须的-e让脚本在第一条失败命令处停下避免带病继续-u防止变量未定义导致静默出错pipefail让管道中任何一环失败都算失败。很多时候一键脚本「跑完了但没做对」就是这三项没开。参数说明里最容易踩的坑是${2:-}的默认值。如果用户写了--action但没带值$2为空这里会给默认值。但如果你用$2而不加:-脚本会直接报unbound variable。另一个细节是switch里每个分支的shift数量要匹配--action install占两个位置少shift一次就会把 install 当成下一个 case 去匹配。2.2 系统检测与分支选择为什么不能无脑执行 apt 或 yum「各种 linux 系统」这几个字写起来轻松做起来要命。Debian 系和 RHEL 系不仅包管理器不同连网卡命名、systemd 版本、默认 shell 路径都有差异。一个合格的 one-click 脚本第一件事就是读/etc/os-release拿到发行版 ID 和主版本号。detect_os() { if [[ -f /etc/os-release ]]; then . /etc/os-release OS_ID$ID # ubuntu / centos / debian / rhel ... OS_VERSION_ID${VERSION_ID:-0} echo [os] $PRETTY_NAME (version $OS_VERSION_ID) else echo [os] cannot detect via /etc/os-release 2 exit 3 fi case $OS_ID in ubuntu|debian) PKG_MGRapt-get ;; centos|rhel|rocky|almalinux|fedora) PKG_MGRyum ;; *) echo [os] unsupported: $OS_ID 2; exit 3 ;; esac ARCH$(uname -m) echo [os] arch$ARCH pkg_mgr$PKG_MGR }这里有一个真实场景Ubuntu 20.04 和 22.04 的VERSION_ID分别是 20.04 和 22.04而 CentOS 7 的VERSION_ID是 7但ID是 centos。如果你的脚本只用grep -i ubuntu判断那么 Debian 也会被漏掉。正确姿势是用case逐项匹配并对不认识的系统直接退出而不是自作聪明往下跑。还有一个隐藏分支同是 Ubuntu20.04 的默认 Python 是 3.822.04 是 3.10很多编译型安装脚本会在 22.04 上因为找不到python3-config而失败。所以检测 OS 时最好把VERSION_ID也存成全局变量后面每个安装步骤都能引用。这个阶段如果失败后续所有命令都不该执行这就是「检测先行」的价值。2.3 日志与回滚设计没有后悔药的脚本不值得跑我见过太多一键脚本跑起来满屏输出关了终端就什么都没了。生产环境出问题后你需要的是「发生了什么、改动了什么、能不能还原」三件事。日志至少要包含时间戳、执行阶段、退出码。回滚是另一重保障。安装类脚本最少要把原配置文件备份到backup/时间戳/下修复类脚本则要考虑「如果这次修复把系统弄得更糟怎么恢复」。这个思路写进脚本并不复杂LOG_DIR/var/log/one-click TS$(date %Y%m%d_%H%M%S) BACKUP_DIR/opt/backup/$TS mkdir -p $LOG_DIR $BACKUP_DIR log() { echo [$(date %F %T)] $* | tee -a $LOG_DIR/one-click-$TS.log } backup_file() { if [[ -f $1 ]]; then cp -a $1 $BACKUP_DIR/$(basename $1).orig log backed up $1 fi }tee -a让日志同时出现在屏幕和文件里别用重定向否则用户看不到进度会以为卡死。备份路径带时间戳是为了避免第二次跑脚本把第一次的备份覆盖掉——这是一个高频翻车点。回滚函数不一定要写多复杂但如果你的脚本会替换/etc/apt/sources.list必须把原文件完整备份并在脚本末尾提供--rollback入口逻辑就是「把备份目录里的文件按原名复制回去」。没有回滚的一键脚本本质上是把风险从一个问题变成了两个问题。2.4 最小可复现脚本骨架一个可以抄走的 one-click 模板把上面三节拼起来就是一个能直接落地的雏形。它不安装任何东西但结构完整你可以往do_action里塞任意修复或安装逻辑。#!/usr/bin/env bash set -euo pipefail LOG_DIR/var/log/one-click TS$(date %Y%m%d_%H%M%S) BACKUP_DIR/opt/backup/$TS mkdir -p $LOG_DIR $BACKUP_DIR log() { echo [$(date %F %T)] $* | tee -a $LOG_DIR/one-click-$TS.log; } detect_os() { [[ -f /etc/os-release ]] || { echo no /etc/os-release 2; exit 3; } . /etc/os-release OS_ID${ID:-unknown} OS_VERSION_ID${VERSION_ID:-0} case $OS_ID in ubuntu|debian) PKG_MGRapt-get ;; centos|rhel|rocky|almalinux) PKG_MGRyum ;; *) echo unsupported OS: $OS_ID 2; exit 3 ;; esac log os$OS_ID version$OS_VERSION_ID pkg_mgr$PKG_MGR } do_action() { case $ACTION in repair) log repairing ... ;; install) log installing ... ;; *) log unknown action: $ACTION; exit 2 ;; esac } ACTION${1:-repair} detect_os do_action注意这个骨架里的set -e和do_action的配合如果do_action内部某个命令失败脚本会直接退出log后面那行不会执行。所以每个关键步骤最好单独捕获退出码if ! run_install; then log install step failed, see above exit 1 fi用if !包一层既保留了失败可见性又避免set -e把错误吞成一行退出。这个模板是我的起手式后续所有修复和安装逻辑都挂在do_action里。3. 各种 Linux 系修复的通用套路从包管理器到内核启动参数一键修复里最刚需的就是两类包管理器烂了或者系统引导坏了。前者靠重装软件包解决后者靠 chroot 进救援模式。写进脚本时两者思路完全不同。3.1 包管理器损坏的修复流程dpkg/yum 锁与依赖断裂Debian 系最常见的故障是「dpkg 中断」和「依赖损坏」。表现是apt-get install任何包都报E: dpkg was interrupted或者提示unmet dependencies。修复套路写在脚本里就是几步fix_dpkg() { echo fix interrupted dpkg dpkg --configure -a || { echo dpkg configure failed 2; exit 1; } echo fix broken deps apt-get install -f -y || { echo apt fix broken failed 2; exit 1; } echo update index apt-get update || { echo apt update failed 2; exit 1; } }dpkg --configure -a是处理「配置到一半被 CtrlC」的标准解药。它会把所有 unpacked 但未配置的包重新配置一遍。如果这里失败常见原因是/var/lib/dpkg/lock-frontend被占用所以脚本开头最好做一个锁检测check_lock() { if [[ -f /var/lib/dpkg/lock-frontend ]]; then echo dpkg lock exists, try remove if process not running if ! pgrep -f apt|dpkg /dev/null; then rm -f /var/lib/dpkg/lock-frontend else echo apt/dpkg process running, wait... 2; exit 1 fi fi }直接rm锁是危险操作必须确认没有 apt/dpkg 进程存活。RHEL 系CentOS、Rocky的对应命令是yum-complete-transaction它专门处理因中断留下的transaction残留。用脚本写就是fix_yum() { if command -v yum-complete-transaction /dev/null; then yum-complete-transaction --cleanup-only || true fi yum clean all yum makecache }--cleanup-only只清理已完成事务的残留不会强制处理未完成事务这是一个保守选择。如果你确定系统没有重要业务在跑可以用yum-complete-transaction不带参数让它自动继续所有未完成事务。注意|| true清理失败不应阻断后续步骤但下一步的makecache失败必须报错。3.2 系统引导与内核问题的修复grub 修复的自动化边界引导修复比包管理修复更危险因为一旦 grub 写坏可能连系统都进不去。一键脚本能做的是在系统还能启动时提前修复grub配置或者生成一份修复引导的辅助脚本而不是替用户执行grub2-install到错误的磁盘。常见的软件层面修复是重建 grub 配置fix_grub() { if [[ -f /etc/default/grub ]]; then # 先备份再判断用 update-grub 还是 grub2-mkconfig cp /etc/default/grub $BACKUP_DIR/grub.default.bak if command -v update-grub /dev/null; then update-grub elif command -v grub2-mkconfig /dev/null; then grub2-mkconfig -o /boot/grub2/grub.cfg else echo no grub update tool found 2; exit 1 fi fi }这里有个明显的发行版差异Ubuntu/Debian 用update-grubCentOS/RHEL 用grub2-mkconfig -o /boot/grub2/grub.cfg。如果你在 CentOS 上跑update-grub会直接 command not found反过来在 Ubuntu 上跑grub2-mkconfig也能运行但容易写错路径。所以脚本里command -v的存在性检测比case分支更可靠。这个环节的自动化边界要守住脚本只允许在「当前系统能启动、挂载正常」时修改配置文件。如果你的目标是修复「起不来的系统」应该让脚本生成一个 chroot 救援命令集让用户在有 LiveCD 时手动执行而不是在跑着的系统里乱写引导扇区。把这些边界写清楚是脚本设计者专业性的体现。3.3 环境安装的幂等性设计重复跑不翻车「一键脚本跑两遍就出问题」是最常见的劝退理由。要做到幂等核心是「先检查再安装」和「用标记文件记住状态」。install_docker() { if command -v docker /dev/null 21; then echo docker already installed, skip return 0 fi case $PKG_MGR in apt-get) apt-get install -y docker.io ;; yum) yum install -y docker ;; esac systemctl enable --now docker }command -v docker检查是最简单的幂等门。但注意docker命令存在不等于守护进程正常所以后面还得跟一个systemctl is-active docker的检查。标记文件的方法是在安装成功后写入/etc/one-click/docker.installed下次检测到该文件就跳重装。这比命令检测更明确因为你可能装了多个版本但脚本只需要知道它是自己装的还是系统自带的。幂等还有一个隐藏问题set -e遇到「检查失败」也会退出。比如上面command -v docker如果不存在返回非零set -e直接让脚本终止。所以在条件判断里必须把检查放进if或加|| true否则幂等检查本身就会弄死脚本。这一点新手很容易忽略看到脚本报command not found就以为安装失败实际是set -e在作怪。4. 服务器环境安装脚本LNMP、MySQL、Docker 这些坑怎么绕安装类脚本是修复类脚本的进阶。修复是「把坏的修回能用」安装是「从零到生产可用」后者要决策的东西更多装哪个版本、用什么方式装、装完怎么跑起来。4.1 安装脚本应该把版本固定还是追新这是第一个必须面对的决策。我见过一个安装 LNMP 的脚本apt-get install nginx跑完过了一个月再跑同一个脚本装的版本从 1.18 跳到了 1.24行为跟着变了。做 one-click 脚本版本号必须显式管起来。NGINX_VERSION1.24.0 MYSQL_VERSION8.0.36 PHP_VERSION8.2 install_nginx() { case $PKG_MGR in apt-get) apt-get install -y nginx$NGINX_VERSION || { echo target version not in repo, fallback to distro default 2 apt-get install -y nginx } ;; yum) yum install -y nginx-$NGINX_VERSION || yum install -y nginx ;; esac }为什么不用latest因为生产环境要可复现今天跑脚本成功下周跑应该得到同样版本的环境。latest会让你的脚本变成「随机结果生成器」。但固定版本也有坑发行版仓库里的 nginx 版本往往比官方源旧比如 Ubuntu 20.04 仓库里是 1.18你想装 1.24 得先添加 Nginx 官方源。所以更稳妥的做法是「用官方安装源 固定版本」双保险apt-cache madison nginx能列出可选版本脚本里可以先查再装。4.2 编译安装与二进制分发的取舍MySQL、PHP 这类组件发行版包管理器提供的版本可能不满足你比如需要特定补丁这时有人会写编译安装脚本。但编译安装是一把双刃剑可控但耗时长、依赖多、升级麻烦。我的建议是能拿官方二进制或发行版包就不要编译。只有两种情况才考虑编译仓库里没有你需要的版本或者你需要自定义编译参数比如 PHP 要指定--with-fpm-user。如果确实要编译脚本里至少要做到compile_mysql() { tar xzf mysql-$MYSQL_VERSION.tar.gz cd mysql-$MYSQL_VERSION cmake . -DCMAKE_INSTALL_PREFIX/usr/local/mysql \ -DMYSQL_DATADIR/data/mysql \ -DSYSCONFDIR/etc/mysql make -j$(nproc) || { echo build failed, see log 2; exit 1; } make install }make -j$(nproc)用全部核编译但这在生产服务器上是危险的——编译会吃满 CPU影响在线业务。脚本应该把这个值开放成参数比如JOBS${JOBS:-2}默认 2让用户自己调。编译日志要写入文件别让用户盯着滚动屏幕等半小时。编译安装的卸载路径也要在脚本里预留否则make uninstall基本形同虚设这是编译类脚本最常见的「烂尾」。表格对比三种安装方式方式优点缺点适用发行版包依赖自动解决、升级方便版本滞后、定制困难80% 场景官方二进制版本新、与官方一致依赖库可能不匹配MySQL/Redis 等源码编译完全可控耗时长、维护难特殊参数或旧版本4.3 环境变量与 systemd 服务的持久化装完软件脚本只做了一半。另一半是把它们注册成服务并让环境变量在下次登录时还在。很多一键脚本跑完mysql能顺手用但重启后服务没了或者换个用户找不到mysql命令。setup_env() { ENV_FILE/etc/profile.d/one-click-env.sh cat $ENV_FILE EOF export MYSQL_HOME/usr/local/mysql export PATH\$MYSQL_HOME/bin:\$PATH EOF chmod 644 $ENV_FILE } setup_systemd() { cat /etc/systemd/system/myapp.service EOF [Unit] DescriptionMyApp Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/myapp server Restarton-failure [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable --now myapp }注意cat写文件时$PATH变量在 heredoc 里会被展开。上面用\$PATH转义保证写入的是字面量$PATH这样每个用户登录时才会动态获取自己的 PATH。如果你不转义写进去的将是脚本执行时的 PATH 快照换一个环境就失效。systemd单元里Restarton-failure是生产环境的基本配置保证进程非正常退出时自动拉起。但要注意别设Restartalways搭配Typeoneshot那会导致循环拉起。服务文件写完后必须daemon-reload否则新 service 不会被识别。这些细节比安装命令本身更能决定一个 one-click 脚本能不能真正「一键」。5. 避坑一键脚本最常见的 5 个翻车现场写一键脚本的难点不是代码量而是你没法替所有用户测试所有系统。以下是我在真实环境里踩过、也帮别人排过的五类高频问题每条都按现象、原因、解决记录。5.1 现象脚本在 CentOS 7 上跑一半崩溃现象是执行到yum install -y epel-release时报Error: Package: epel-release-7-14 noarch (extras)或者直接报Cannot find a valid baseurl for repo: base/7/x86_64。原因很扎心CentOS 7 已停止维护原来的mirror.centos.org源全部失效。很多脚本的安装步骤里还写着硬编码的旧源地址。解决脚本里检测到centos且VERSION_ID7时手动把 repo 源切换到vault.centos.org并先跑yum clean all。代码片段fix_centos7_repo() { if [[ $OS_ID centos $OS_VERSION_ID 7 ]]; then sed -i s|mirror.centos.org|vault.centos.org|g /etc/yum.repos.d/*.repo sed -i s|^#baseurl|baseurl|g /etc/yum.repos.d/*.repo sed -i s|^mirrorlist|#mirrorlist|g /etc/yum.repos.d/*.repo yum clean all fi }这个坑提醒我任何写得好的修复脚本第一件事不是「修」而是「判断系统还活着吗、源还通吗」。源失效属于环境层面的故障装什么都会失败。5.2 现象重启后服务起不来现象是脚本跑完当时一切正常systemctl status显示 active (running)但重启服务器后服务消失。原因常常是服务文件被写进了/tmp或脚本自己的临时目录。systemd只会在重载时读取/etc/systemd/system/下的单元写在/tmp的重启后即被清空。解决统一把服务文件写到/etc/systemd/system/并且写完后立即systemctl daemon-reload。我在脚本里加了一个校验if [[ ! -f /etc/systemd/system/myapp.service ]]; then echo service file missing in /etc/systemd/system, will reinstall 2 setup_systemd fi另一个隐藏原因是enable没生效。只start不enable下次启动不会自动拉起来。脚本里应该用systemctl enable --now myapp而不是分两步执行避免漏掉enable。5.3 现象apt-get update 卡死现象是脚本运行到apt-get update时长时间无响应或报Could not get lock /var/lib/dpkg/lock-frontend。原因通常是上一次 apt 进程异常退出锁文件残留或者有后台的unattended-upgrades还在跑。解决脚本启动时先检测锁和 apt 进程。如果存在apt或dpkg进程就输出提示并等待绝不直接删锁。如果确认没有进程再清理锁。我把这段写成公共函数所有 Debian 系分支开头都调用wait_for_apt() { for i in {1..30}; do if pgrep -x apt /dev/null || pgrep -x apt-get /dev/null; then sleep 2 else break fi done # 经过 60 秒仍占用报错退出不执行 rm if pgrep -x apt /dev/null; then echo apt still running, manual intervention needed 2 exit 1 fi }记住一句话脚本里永远不要主动rm dpkg/lock除非你能 100% 确认没有相关进程。让锁留着并报错好过删锁后把 dpkg 状态搞坏。5.4 现象同一脚本在 Ubuntu 20.04 与 22.04 行为不一致现象是同一份安装脚本在 20.04 上成功装好 MySQL在 22.04 上报ERROR: Unable to start MySQL或者 PHP 扩展编译失败。原因不是脚本命令错而是两个系统的默认软件版本差异20.04 默认 Python 3.8、MySQL 8.0.2122.04 默认 Python 3.10、MySQL 8.0.35个别库的 ABI 变了。解决脚本检测到VERSION_ID22.04时走一套独立的参数配置。比如 MySQL 的初始化命令在 22.04 上需要--initialize-insecure而 20.04 可以直接用初始化脚本。我在脚本里把版本差异抽象成变量MYSQL_INIT_ARGS if [[ $OS_ID ubuntu $OS_VERSION_ID 22.04 ]]; then MYSQL_INIT_ARGS--initialize-insecure fi mysqld $MYSQL_INIT_ARGS所以脚本里凡是涉及「版本行为差异」的都要按主版本分支而不是按发行版分支。否则你以为支持了多种 linux实际只支持了 Linux 的一个子集。5.5 现象脚本输出乱码且中文注释丢失现象是脚本里的中文 echo 在终端显示为??或者用sed改配置文件时中文注释被删掉。原因通常是两个脚本文件本身不是 UTF-8 编码以及执行环境LANG变量不是C.UTF-8或相应的中文 locale。解决脚本头部强制导出语言环境并确保文件保存为 UTF-8export LANGC.UTF-8 export LC_ALLC.UTF-8C.UTF-8是多数 Linux 发行版默认就有的 locale比zh_CN.UTF-8更保险因为后者可能需要额外安装。另外写文件时用cat file EOF比echo拼接更不容易出现编码错位。如果脚本里有sed -i处理包含中文的配置建议先备份原文件再操作避免编码转换把内容搞丢。这个坑很小但往往是最难排查的——用户看不到错误只看到一堆乱码第一反应是你脚本写坏了。6. 把脚本做成真正的 one-click参数默认值、退出码与验证清单一个脚本能不能被团队长期使用看的不是首次跑通而是第二次、第十次跑还稳不稳。我习惯在脚本末尾加一个自检函数执行完所有操作后用几条命令验证而不是让用户自己盯着输出判断。verify_installation() { FAIL0 command -v nginx /dev/null || { echo nginx missing; FAIL1; } systemctl is-active --quiet nginx || { echo nginx not running; FAIL1; } command -v mysql /dev/null || { echo mysql missing; FAIL1; } if [[ $FAIL -eq 0 ]]; then echo ALL CHECKS PASSED else echo SOME CHECKS FAILED, see above 2 exit 1 fi }退出码规范也要定死0 代表成功1 代表安装或修复失败2 代表参数错误3 代表系统不支持。这样外面再包CI或监控系统时不需要读日志就知道脚本状态。默认参数上我坚持「所有可能变化的量都能被覆盖」版本号、安装路径、数据目录、服务名都应该支持环境变量或--xxx覆盖而不是写死在脚本里。我第一次写一键脚本时把所有路径写死后来换到另一台服务器要改动十几处从那以后每个变量都走默认值加可覆盖的路线。对于修复类脚本最后一件事是提示用户检查关键服务状态而不是直接宣告胜利。我会在脚本末尾打印三行常用的验证命令比如nginx -t、mysql -e select 1、df -h让用户自己确认。这不是甩锅是承认脚本的边界它保证操作正确但无法保证你的业务数据完整。这是我的教训也是我希望你知道的底线——把验证步骤留给自己脚本才敢于做出修复动作。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?