ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux一键修复与安装脚本:系统救援与环境部署实战指南

Linux一键修复与安装脚本:系统救援与环境部署实战指南 简介面向Linux运维与服务器环境搭建的自动化脚本集合以“一键”方式提供系统修复与安装能力。覆盖Ubuntu、CentOS、Debian等常见发行版适用于运维人员、系统管理员和Linux初学者可完成系统检测、包管理修复、引导加载器重置、网络配置以及Web/数据库/邮件等服务器环境部署。资源共24个文件、约44KB主体为17个shell脚本承担核心修复与安装逻辑另有2个YAML配置、2个文本说明、2个Markdown笔记和1个许可证整体轻量便于分发。已有153人学习下载。脚本内置sudo权限检查、时间同步、文件修改、日志收缩、软件包管理等模块并提供多语言开发环境安装入口支持Bash编写以保证跨发行版兼容同时设计错误检测与恢复机制以降低操作风险。附带文档可帮助快速掌握各脚本用途、执行条件与排错思路适合用于服务器初始化、故障恢复与环境迁移等场景。1. 一键修复与安装脚本先救活系统再搭好环境接手一台被折腾到起不来的 Linux 服务器或者给新开的机器把整套运行环境装齐这两件事的共同痛点是步骤特别长、命令特别碎、每台机器的脾气还都不一样。一键修复与安装脚本One-click就是为这两类高频场景设计的——把所有 linux 系统修复和服务器环境安装的常见操作收敛到一个入口脚本里分修复、安装两个分支去跑跑完留下日志。它能解决的不只是“省敲几行命令”而是把你在 linux 系统故障案例里看到的那些重复诊断动作固化成流程让新手照跑不慌让老手把时间省下来去处理真正疑难的问题。2. 为什么一键脚本能修系统模块化、幂等与日志三板斧修复和安装脚本最怕的不是功能少而是发生在一台“你不了解状态”的机器上。它能不能信得过取决于脚本本身的三个设计入口是否模块化、重复执行是否安全、出问题能不能追溯。这三点想清楚了后面每个分支函数才有讨论的价值。2.1 脚本骨架一个入口、两个分支、分文件管理我写这类脚本不会把几千行塞进一个文件。常见做法是把入口做成很薄的调度器每个分支独立成repair_*、install_*这样的函数文件入口只负责识别系统、转发参数、记录日志。下面是最小的入口骨架#!/bin/bash # 一键修复与安装脚本 - 入口 # 用法: ./one-click.sh repair [target] # ./one-click.sh install [target] set -u export LC_ALLC LOG_FILE/var/log/one-click-$(date %F).log SYS_ID SYS_VER PKG_MGR detect_system() { if [ -f /etc/os-release ]; then . /etc/os-release SYS_ID${ID:-unknown} SYS_VER${VERSION_ID:-unknown} else echo $(date %T) 未找到 /etc/os-release无法识别发行版退出 | tee -a $LOG_FILE exit 1 fi } detect_pkg_mgr() { case $SYS_ID in debian|ubuntu) PKG_MGRapt ;; centos|rhel|rocky|almalinux|fedora) PKG_MGRdnf ;; sles|opensuse*) PKG_MGRzypper ;; *) PKG_MGRunsupported ;; esac } main() { detect_system detect_pkg_mgr echo $(date %T) 系统: $SYS_ID $SYS_VER 包管理器: $PKG_MGR 动作: ${1:-} | tee -a $LOG_FILE case ${1:-} in repair) repair_main ${2:-} ;; install) install_main ${2:-} ;; *) echo 用法: $0 {repair|install} [target] ;; esac } main $这段代码的逻辑很简单先识别系统再分发动作。注意我用了set -u而不是set -e。原因在于修复场景里某个子命令失败是常态比如grub-install在 UEFI 机器上会报错但机器未必真的救不回来此时直接退出反而堵死了后面 chroot 里补修的机会。用set -u只挡未定义变量具体函数的成败交给函数内部判断出错路径由trap或逐条日志记录来兜底。入口里的tee -a值得一提。所有关键信息同时输出到终端和日志文件用户在跑长任务时能看到进度事后排查时也有时间线。日志文件名带了日期避免一天多次运行互相覆盖。这比满屏 echo 却什么都留不下要实用得多。2.2 系统识别与包管理器分发别把 apt 命令丢到 yum 机器上脚本能不能跨发行版复用核心就在系统识别这一步。很多人习惯用uname -a判断系统这其实是误区——它给的是内核版本而内核和发行版没有一一对应关系。同样内核版本的机器可能是 Ubuntu 也可能是 Rocky Linux包管理器完全不同。可靠的来源是/etc/os-release它由 systemd 时代以来的发行版统一提供字段稳定。解析之后把包管理器的选择做成 case 分支Debian/Ubuntu 走 aptRHEL 系走 dnf 或 yumSUSE 系走 zypper。判断完包管理器后续所有动作例如“安装编译依赖”“修复依赖关系”都统一走pkg_install这类封装函数而不是散落成裸命令pkg_install() { case $PKG_MGR in apt) apt-get update -qq apt-get install -y --no-install-recommends $ ;; dnf) dnf install -y $ ;; zypper) zypper install -y $ ;; *) echo 暂不支持该包管理器请手动安装: $* | tee -a $LOG_FILE ;; esac }这个封装的价值在统一超时和日志。比如 apt 系一定要用apt-get而不是aptapt是给交互终端用的它带进度条和颜色转义符在脚本非交互环境下行为不够稳定重定向日志时还会混入控制字符。--no-install-recommends是 Debian 系装编译依赖时的常用参数它避免把文档、可选插件等连带装进来减少依赖面也降低后面autoremove误删的误伤面。如果你维护的机器里有国产发行版比如基于 openEuler 或龙蜥的发行版它们的/etc/os-release的ID字段可能是openeuler、anolis。策略上不必为每个名字单独写一套而是判断“它是否兼容 dnf/yum”把 ID 映射到包管理器族上。我一般会在 case 分支里把已知的兼容名都列出来并留一个 unsupported 出口提示人工处理而不是猜。2.3 幂等设计和日志归集重复跑不会翻车跑挂了能查原因“一键”听起来爽但很多翻车现场是同一脚本被反复执行造成的。比如装 MySQL第一次编译到一半失败第二次从头再编译旧目录里的残留 Makefile 和新源码混在一起报错千奇百怪。好的修复安装脚本必须讲幂等同一个命令跑一次和跑两次最终状态一致或者至少不会更坏。我常用的两个手段是状态标记和残留清理。状态标记适合安装分支比如编译安装完成后写一个.one-click-stamp文件下次执行检测到标记就提示“已安装如需重装请先 uninstall 或加 --force”。残留清理适合修复分支比如修复包管理器前先把损坏的锁文件备份而不是直接删除然后才重试。写入和校验放一起install_marker() { local marker$1 if [ -f $marker ]; then echo $(date %T) 检测到标记文件 $marker跳过安装。如需重装请删除后重试 | tee -a $LOG_FILE return 1 fi touch $marker } cleanup_stale_lock() { local lock_path$1 if [ -f $lock_path ]; then mv $lock_path ${lock_path}.bak.$(date %s) fi }标记文件有几个注意点。不要把它和安装版本信息混在一个文件里因为版本升级时旧标记会让脚本误判为已安装我习惯在标记文件里写两行内容安装时间和版本号。cleanup_stale_lock里用mv而不是rm保留原始锁文件方便事后对照这也算是一种后悔药。日志归集是另一块。修复和安装过程会调用大量外部命令给每个外部命令单独写日志不现实。我会在函数里用一个带时间戳的全局日志函数把所有输出合并到LOG_FILE并且用trap在脚本退出时把结束状态写进去log_info() { echo $(date %T) [INFO] $* | tee -a $LOG_FILE; } log_error() { echo $(date %T) [ERROR] $* | tee -a $LOG_FILE; } finish() { local code$? echo $(date %T) [EXIT] $code | tee -a $LOG_FILE exit $code } trap finish EXITtrap finish EXIT的关键效果是即使脚本中途被kill或因为某条命令让 shell 退出日志也会被刻上退出码。排查一键脚本问题时最常做的一件事就是打开日志文件看最后三行——是正常结束还是被截断截断的最后一条日志往往就是第一个出问题的点。没有这套机制你只会在终端看到 “failed”无从下手。3. 系统修复分支从引导、包管理器到网络源的救援函数拆开讲修复分支。这里要处理的是 linux 系统故障案例里最高发的几类引导损坏、包管理器状态错乱、网络配置丢失、动态链接库缺失。每一类都对应一到两个可以在脚本里复用的函数我会把关键参数和适用边界说明白。3.1 引导损坏chroot 环境重置 GRUB引导损坏的典型现象是开机停在黑屏或 grub rescue 提示符进不了系统。这类问题通常不是 grub 二进制真坏了而是/boot分区内容丢失、磁盘分区表变动或者内核升级后 grub.cfg 没更新。修复思路是用 Live CD 启动把原系统的根分区挂载起来再通过 chroot 切进原系统环境重装 grub 并重新生成配置。repair_bootloader() { local root_dev$1 local boot_dev${2:-} local mnt/mnt/rescue [ -z $root_dev ] log_error 修复引导需要指定根分区例如 /dev/sda2 return 1 mkdir -p $mnt mount $root_dev $mnt || { log_error 挂载根分区失败检查分区路径; return 1; } [ -n $boot_dev ] mount $boot_dev $mnt/boot mount --bind /proc $mnt/proc mount --bind /sys $mnt/sys mount --bind /dev $mnt/dev chroot $mnt /bin/bash -c grub-install ${GRUB_TARGET_DEV:?请设置目标磁盘} update-grub log_info 引导修复完成退出 chroot 并卸载分区 umount $mnt/dev $mnt/sys $mnt/proc [ -n $boot_dev ] umount $mnt/boot umount $mnt }函数里最容易错的是挂载顺序。必须先挂根分区再挂 boot 和虚拟文件系统卸载时顺序反过来。mount --bind把当前 Live 环境的/proc、/sys、/dev映射进 chroot让原系统里的 grub 命令能正常读取设备状态。GRUB_TARGET_DEV是指 grub 要写到的磁盘设备比如/dev/sda这块是纯机械硬盘时写盘名NVMe 固态则可能是/dev/nvme0一定要传盘而不是分区否则引导程序找不到 Stage 1。chroot 之后建议先用df -h确认根分区空间够不够。如果之前是因为/boot写满导致 grub 更新失败空间不足时update-grub会再次失败。空间不足就先清理再执行别硬跑。3.2 包管理器状态修复dpkg 与 rpm 数据库重建包管理器状态损坏是很隐蔽的故障系统能正常登录但一装软件就报 “dpkg was interrupted” 或 “rpmdb open failed”。这类问题不影响业务进程但会让任何后续安装卡死。修复策略分发行版走。Debian/Ubuntu 系用 dpkg 审计加配置修复repair_dpkg() { log_info 开始修复 dpkg 状态 mkdir -p /var/lib/dpkg/backup if [ -f /var/lib/dpkg/status ]; then cp /var/lib/dpkg/status /var/lib/dpkg/backup/status.$(date %s) fi dpkg --audit 21 | tee -a $LOG_FILE dpkg --configure -a 21 | tee -a $LOG_FILE apt-get install -f -y 21 | tee -a $LOG_FILE log_info dpkg 修复完成 }dpkg --audit只列问题不修改适合先看破坏程度。dpkg --configure -a是重跑所有未完成的配置步骤很多中断残留在这里被补齐。apt-get install -f用于修复依赖关系它会尝试把破损的包恢复为可用状态。备份 status 文件是修复脚本的保底操作万一修复过程中把包状态改坏还能拿原始状态回去对比这对排查“修复反而修出新问题”很重要。RHEL 系则是重建 rpm 数据库repair_rpmdb() { log_info 开始重建 rpm 数据库 local db_dir/var/lib/rpm if [ -f $db_dir/__db.001 ]; then for f in $db_dir/__db.*; do mv $f ${f}.bak.$(date %s) done fi rpm --rebuilddb rpm -qa /dev/null 21 log_info rpm 数据库重建成功可正常查询 || log_error 重建后 rpm -qa 仍失败检查磁盘空间 }rpm 数据库损坏多数表现为rpm -qa直接卡死或报 “cannot open Packages database”。重建前把__db.*这些 Berkeley DB 的锁文件挪走相当于清掉进程残留的锁状态再执行rpm --rebuilddb重建索引。最后用rpm -qa做验证能列出包列表才算真正修好。这一步如果验证失败最普遍的原因是磁盘满了索引写不进去别反复 rebuild先清理空间。3.3 网卡、DNS 与软件源配置修复服务器起不来网络修复脚本需要覆盖三种情况网卡没启动、DNS 配置被覆盖、软件源指向不可达地址。修网卡前先确认 NetworkManager 是否在运行repair_network() { log_info 开始检查网络状态 systemctl status NetworkManager --no-pager 21 | head -20 | tee -a $LOG_FILE local active_iface active_iface$(ip -br link | awk $2!lo $3UP {print $1; exit}) if [ -z $active_iface ]; then local first_iface first_iface$(ip -br link | awk $2!lo {print $1; exit}) log_info 没有活跃网卡尝试拉起 $first_iface nmcli device connect $first_iface 21 | tee -a $LOG_FILE fi grep -q ^nameserver /etc/resolv.conf || echo nameserver 223.5.5.5 /etc/resolv.conf log_info 网络配置检查完成 }ip -br link输出简洁适合脚本解析。这里选 223.5.5.5 只是作为能用的公共 DNS 示例实际部署时我更建议改成公司内部 DNS 或你确认可达的地址并且先ping验证连通性再写入。修复脚本里不要默认改动现有 DNS 配置——只做兜底也就是当前文件里没有 nameserver 时才写避免把用户手动配的 DNS 覆盖掉。软件源修复相对高频尤其是从旧系统升级或换源后出现的 404。修复脚本要做的就是先把原始源文件备份再按发行版和版本号写入可用的源模板repair_apt_source() { local sources/etc/apt/sources.list [ -f $sources ] cp $sources ${sources}.bak.$(date %s) cat $sources EOF deb http://deb.debian.org/debian ${SYS_VER} main contrib non-free deb http://security.debian.org/debian-security ${SYS_VER}-security main contrib non-free EOF apt-get update -qq 21 | tee -a $LOG_FILE }实际使用时${SYS_VER}这个代号需要额外处理Debian 的/etc/os-release里 VERSION_CODENAME 才是 “bookworm” 这样的代号而不是 “12”。来源里的仓库路径写错apt-get update 就会报 404。我在脚本里会单独提取VERSION_CODENAME做拼接并且更新后强制检查一次 update 的退出码。用公共软件源只是兜底方案内网机器建议在源文件模板里保留变量替换方便部署时传入内网镜像地址。3.4 系统库与动态链接器救援ldd 和 ldconfig 的边界这类故障的症状是命令报 “error while loading shared libraries”比如libssl.so.1.1: cannot open shared object file。常见原因是误删了系统库、升级覆盖了旧版本或者用户手动往/usr/local/lib装了不兼容的库。修复的第一步不是急着下载库文件而是确认缺什么、从哪个路径加载diagnose_libs() { local bin$1 [ -z $bin ] log_error 用法: diagnose_libs /usr/bin/openssl return 1 ldd $bin 21 | tee -a $LOG_FILE ldconfig -p | grep -E $(basename $bin)|libssl|libcrypto | tee -a $LOG_FILE }ldd输出里标着not found的就是缺失项ldconfig -p能看系统当前缓存了哪些库。很多人一看到缺库就百度文件名找到 .so 就丢进系统目录这种操作风险极大——库依赖的是一个完整版本链A 库依赖 B 库的特定符号只拷一个 .so 往往引出一串新报错。正确的修复是优先用包管理器找回原库。比如 libssl 的库属于 openssl 包apt-get install --reinstall openssl或者dnf reinstall openssl能把库文件恢复到和系统匹配的版本。只有确定这个库来自第三方源码安装、确实只能手动处理时才考虑用LD_LIBRARY_PATH临时指向备份目录验证效果。ldconfig本身解决的是缓存索引问题库文件实际不存在时执行ldconfig不会让缺失的库凭空出现别指望它兜底。4. 服务器环境安装分支LNMP 与常用组件的最小可用组合安装分支面向的场景很明确一台刚装好系统的空白服务器要变成能跑业务的服务器。这里我以 LNMPLinux Nginx MySQL PHP为例展开这套组合能覆盖大多数入门和中小业务需求。安装脚本的思路也适用于其他组合。4.1 版本变量和路径约定安装脚本的第一道闸门安装脚本最忌讳“装最新版”。最新版往往意味着新特性、新依赖也意味着社区踩坑经验最少。我在脚本顶部把版本和路径全部收敛成变量后续所有函数只引用变量不出现裸版本号NGINX_VERSION${NGINX_VERSION:-1.24.0} PHP_VERSION${PHP_VERSION:-8.1.27} MYSQL_VERSION${MYSQL_VERSION:-8.0.36} REDIS_VERSION${REDIS_VERSION:-7.2.4} INSTALL_PREFIX${INSTALL_PREFIX:-/usr/local} NGINX_PREFIX$INSTALL_PREFIX/nginx PHP_PREFIX$INSTALL_PREFIX/php MYSQL_PREFIX$INSTALL_PREFIX/mysql SRC_DIR${SRC_DIR:-/usr/local/src} DATA_DIR${DATA_DIR:-/var/lib/mysql}这些变量值是有考量的。Nginx 1.24 是当前维护稳定的主线版本PHP 8.1 兼容绝大多数存量业务代码MySQL 8.0 是长期支持版。版本号支持通过环境变量覆盖意味着这个脚本在同一套基线版本上还能灵活升级。路径统一放在/usr/local下符合 Linux 的文件系统层次标准也便于后续卸载或迁移。编译安装前一个必须做的动作是把SRC_DIR下的同名旧目录清掉避免源码版本和缓存冲突。4.2 编译安装 LNMP下载、编译参数与多线程控制源码编译安装的通用套路是下载源码包、校验完整性可选、解压、configure、make、make install。下面用 Nginx 演示完整函数MySQL 和 PHP 只是参数不同install_nginx() { local src_tarball$SRC_DIR/nginx-${NGINX_VERSION}.tar.gz local src_dir$SRC_DIR/nginx-${NGINX_VERSION} [ -f $src_tarball ] || wget -O $src_tarball https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz [ -x $NGINX_PREFIX/sbin/nginx ] log_info nginx 已安装跳过 return 0 cd $SRC_DIR || return 1 tar xzf $src_tarball cd $src_dir || return 1 ./configure --prefix$NGINX_PREFIX \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-stream \ --with-http_stub_status_module \ --pid-path$NGINX_PREFIX/logs/nginx.pid make -j$(nproc) make install log_info nginx 安装完成安装路径 $NGINX_PREFIX }关键编译参数说明--with-http_ssl_module是 HTTPS 必需--with-http_v2_module对应 HTTP/2--with-stream支持 TCP/UDP 代理--with-http_stub_status_module提供/stub_status页面做健康检查。make -j$(nproc)用满全部 CPU 核数但这在小内存机器上有风险后面避坑章节会展开讲。configure 阶段失败时日志里已经保留了具体缺哪个开发库按提示补装即可。PHP 的 configure 里通常要显式指定--with-fpm-userwww --with-fpm-groupwww --with-openssl --with-zlib --enable-fpm。这个--enable-fpm特别容易漏漏了编译出来的 PHP 没有 php-fpm 管理脚本后面配 systemd 就缺主程序。MySQL 则用编译时间最长的 cmake 流程核心参数是-DCMAKE_INSTALL_PREFIX和-DMYSQL_DATADIR此外-DSYSCONFDIR/etc决定配置文件位置这三项务必写清楚其它默认即可。4.3 systemd 注册与开机自启service 文件怎么写不翻车编译安装不会自动注册 systemd 服务这是“装完了但起不来”的头号原因。手动启动 Nginx 能看到进程但重启机器后就没了。systemd 里注册服务分两步写 unit 文件然后daemon-reload让 systemd 读取它register_nginx_systemd() { cat /etc/systemd/system/nginx.service EOF [Unit] Descriptionnginx server Afternetwork.target [Service] Typeforking ExecStart$NGINX_PREFIX/sbin/nginx ExecReload$NGINX_PREFIX/sbin/nginx -s reload ExecStop$NGINX_PREFIX/sbin/nginx -s quit PIDFile$NGINX_PREFIX/logs/nginx.pid Restarton-failure RestartSec5s [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable nginx.service systemctl start nginx.service }Nginx 是典型的多进程 forking 模型主进程启动后 fork 出 worker 进程主进程会退出前台。所以 unit 文件必须用Typeforking并配合PIDFile告诉 systemd 主进程的 PID 写在哪里。如果误写成Typesimplesystemd 会认为主进程一启动就退出从而判定服务失败。Restarton-failure表示非正常退出时才拉起业务主动 stop 不会被误拉RestartSec防止故障时疯狂重启打日志。这里的$NGINX_PREFIX在写文件前已被展开成具体路径注意 cat 用的 heredoc 没加引号这就是刻意的变量展开。4.4 附加组件Redis、Python 虚拟环境和 PHP 扩展安装分支通常不止 LNMP 三件套。Redis 作为缓存组件出现频率很高编译安装相对轻量install_redis() { local src_dir$SRC_DIR/redis-${REDIS_VERSION} [ -x $INSTALL_PREFIX/redis/bin/redis-server ] log_info redis 已安装跳过 return 0 cd $src_dir || return 1 make -j$(nproc) make install PREFIX$INSTALL_PREFIX/redis cp $src_dir/redis.conf $INSTALL_PREFIX/redis/redis.conf # 以非 root 用户运行降低风险 useradd -r -s /sbin/nologin redis 2/dev/null || true sed -i s/^# supervised auto/supervised systemd/ $INSTALL_PREFIX/redis/redis.conf sed -i s|^dir ./|dir $DATA_DIR| $INSTALL_PREFIX/redis/redis.conf log_info redis 安装完成配置文件在 $INSTALL_PREFIX/redis/redis.conf }Redis 默认配置有几个坑默认以 root 运行、日志目录在当前目录、daemonize默认 no。编译安装后最少改两处一是supervised systemd让 Redis 能被 systemd 管理二是dir指向实际数据目录。useradd -r创建系统用户把服务降权到非 root这是生产服务器的基本习惯。Redis 的数据目录要提前建好并 chown 给 redis 用户否则启动时写不了 RDB 文件现象就是进程起来几秒又退出。Python 环境在服务器上的处境比较特殊。系统自带的 Python 通常被系统工具依赖操作系统的包管理器升级它可能弄坏系统组件。所以我装 Python 一律用源码安装加venv虚拟环境业务代码跑在虚拟环境里和系统 Python 隔离install_python() { local py_ver3.11.8 local prefix/usr/local/python-${py_ver} [ -x $prefix/bin/python3 ] log_info python $py_ver 已安装 return 0 cd $SRC_DIR || return 1 tar xzf Python-${py_ver}.tgz cd Python-${py_ver} || return 1 ./configure --prefix$prefix --enable-optimizations make -j$(nproc) make install $prefix/bin/python3 -m venv $INSTALL_PREFIX/venv/app log_info python 已安装虚拟环境在 $INSTALL_PREFIX/venv/app }--enable-optimizations会做 profile-guided optimization编译时间长一倍但解释器运行性能更好。对计算类业务影响明显对纯 web IO 业务则收益有限机器性能弱时可以去掉。venv 创建后业务代码的依赖全部通过$INSTALL_PREFIX/venv/app/bin/pip install安装不污染系统 site-packages升级 Python 或迁移机器时也有干净边界。5. 避坑修复脚本和安装脚本最容易翻车的五个现场脚本我写了很多轮踩过的坑比看过的教程多。这五个现场基本把新手的坑和老手的坑都覆盖了逐一按“现象→原因→解决”写清楚。5.1 现象Debian 系机器上脚本跑一半报 “package not found”用的一键脚本在 Debian 12 上跑到安装依赖那步直接失败提示找不到包。原因大概率是脚本里用了apt install而不是apt-get install或者apt-get update没有先执行。apt是面向交互的命令在脚本非交互环境下对部分参数的处理不如apt-get稳定而包索引过期时任何 install 都会报找不到包。解决脚本内统一封装包管理器命令首次安装前先 updateinstall 用--no-install-recommends缩减依赖面。如果脚本是从 CentOS 那套迁移过来的还要注意 CentOS 7 的包管理器是 yumCentOS 8 变成 dnf两者兼容但 dnf 的 repo 配置语法不同不能直接照搬。5.2 现象重装 Nginx 后 systemctl start nginx 失败端口被占用给一台跑过业务的机器重装环境新 Nginx 启动时报bind() to 0.0.0.0:80 failed。原因不是新装的服务配置错了而是旧 Nginx 的 master 进程还活着或者编译安装的旧路径/usr/local/nginx和 systemd unit 里的新路径不一致。很多重装脚本直接覆盖文件却不管已经在跑的进程。解决安装前先探测端口和进程ss -lntp | grep :80找到 PIDps -fp pid确认是不是旧 nginx是则先nginx -s quit或 kill。随后再检查 unit 文件里的PIDFile路径和ExecStart路径是否和新安装位置一致不一致时daemon-reload也不会生效必须修改 unit 文件。5.3 现象脚本里 export 了环境变量但登录终端后 PATH 就是没变安装脚本在最后往/etc/profile.d/写了一个env.sh里面 export 了 PATH脚本执行完也确实 echo 出来变量正确可用户重新登录后却找不到新装的命令。原因是没有理解登录 shell 和非登录 shell 的读取差异。/etc/profile.d/*.sh在 login shell 里被读取但很多运维习惯开 screen/tmux 或直接执行bash进入子 shell这些场景读的是~/.bashrc不读 profile。解决安装脚本里给当前用户写一份~/.bashrc的追加配置并提示用户重新登录或手动source。更稳妥的是把关键可执行文件做个软链到/usr/local/bin因为这个目录通常已经在 PATH 里能绕开 shell 配置差异。5.4 现象小内存机器编译 MySQL 或 PHP 直接 OOM报错误导排查一台 1G 内存的机器执行一键安装编译到一半被系统直接 kill日志尾部是configure: error: C compiler cannot create executables看起来像缺编译器。原因不是 gcc 坏了而是内存耗尽后编译进程被杀临时文件的失败间接体现在 configure 上。解决编译大型项目前检查物理内存free -m低于 2G 时不要用make -j$(nproc)改为make -j1减少并行编译进程数。还可以临时加 swapfallocate -l 2G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile编译完再视情况保留或关闭 swap。MySQL、LLVM 这类重编译项目特别吃内存这是血泪经验。5.5 现象修复依赖时把正在用的服务给卸了一键修复脚本跑到apt-get autoremove清理依赖结果把 nginx 依赖的 libpcre3 给删了重启后站点 500。原因在于 autoremove 会把“被标记为自动安装”的包全部清掉而这个标记在手动编译安装 nginx 时不会建立系统并不认为 libpcre3 被 nginx 依赖于是当作孤儿包清理。解决修复脚本里不要用无脑autoremove先apt-get autoremove --dry-run看清单人工扫一眼有没有不该动的包。更稳的做法是安装前用apt-mark manual libpcre3标记核心运行库为手动安装让 autoremove 跳过它。脚本加幂等标记之外清理类动作永远是“先干跑、再真跑”。6. 自检、回滚和日志审计让一键脚本不背黑锅脚本跑完只是开始确认它真的把活干好了才是收尾的关键。我的习惯是每个安装函数结束后自动跑一轮自检把结果写入日志而不是只有一句“安装完成”。6.1 安装完成后的十秒钟自检命令health_check() { log_info 安装后自检 $NGINX_PREFIX/sbin/nginx -t 21 | tee -a $LOG_FILE systemctl is-active nginx || log_error nginx 未在运行 $MYSQL_PREFIX/bin/mysqladmin ping -u root --silent 21 | tee -a $LOG_FILE $PHP_PREFIX/sbin/php-fpm -t 21 | tee -a $LOG_FILE redis-cli ping 21 | tee -a $LOG_FILE }nginx -t只验证配置文件语法和实际启动不是一回事所以后面要跟一条systemctl is-active。mysqladmin ping返回mysqld is alive才算真活了注意新装 MySQL 默认 root 可能只有 socket 认证用-u root探测的是本机 socket 连接不涉及网络权限问题。php-fpm 的-t同样只检查配置最终以 systemd 状态为准。自检的全套输出进日志而不是只在终端闪现这样真出问题时日志里的自检段就是第一现场。6.2 回滚习惯改了什么、备份在哪、怎么撤一键脚本的价值一半在安装效率另一半在可回滚。我在脚本里统一维护一个操作清单文件每次修改系统配置前先备份备份路径记进清单。比如改/etc/nginx/nginx.conf前先复制一份到同目录.bak.时间戳改/etc/profile.d同理。回滚时不需要脚本自动完成因为自动回滚在全量安装场景里容易误伤新装的文件但“备份在哪”必须随时可查。这份清单文件通常放在/var/log/one-click-changes.log每行记录操作时间、文件路径和备份路径。排查问题的上午、回想脚本到底动了哪些文件的深夜这份日志就是唯一的后悔药。现在写的每个一键脚本我都强制要求自己先加好日志和备份位再写业务逻辑——顺序反了脚本跑得再快也不敢拿到生产环境上重复执行。希望这些思路对你手上的修复和安装脚本方案有帮助。本文还有配套的精品资源点击获取
返回列表