简介:这是一份面向Linux运维人员、系统管理员及初学者的自动化运维工具包,围绕系统故障修复与服务器环境搭建,提供多发行版适配的一键处理方案。压缩包共24个文件,主体为17个shell脚本,涵盖系统检测、包管理器修复、日志清理、网络配置、用户权限调整等常见运维操作;另含2个YAML编排文件、2个Markdown文档、2个TXT配置清单及许可证文件,帮助使用者理解脚本逻辑并快速部署。资源体积仅44KB,轻量实用。目前已有153人参与学习。脚本覆盖Ubuntu、CentOS、Debian等主流系统,并涉及Web服务、数据库及邮件服务器等场景,既支持自动化批处理,也保留交互定制接口;同时内置错误检测与恢复机制,降低误操作风险。通过学习,可掌握一键修复与安装的完整设计思路、Bash脚本编写技巧,以及服务器环境初始化与配置的标准化流程,适合在实际运维中直接参考或二次开发。
1. 一键修复与安装脚本到底是什么:运维的后悔药,还是新的黑匣子?
在做服务器环境安装这件事上,一键修复与安装脚本是我见过争议最大的运维产物之一:有人靠它把一台裸机变成可用的站点环境只要几分钟,也有人因为脚本里一条没做校验的 rm 直接把系统搞崩。这类脚本的本质,是把 Linux 系统修复和服务器环境安装这两类高频、机械、容易漏步骤的操作,固化成可以反复执行的 shell 脚本,解决的是重复劳动和人为失误。适合新接触 Linux 服务器、需要快速交付可用环境的人,也适合要维护几十台发行版混杂机器、不想逐台手工敲命令的运维。但判断一个脚本能不能用,不是看它宣传得多“智能”,而是看它有没有探测、备份、回滚和日志四件事。
2. 拆解一键脚本的工作流程:从黑匣子到可审阅的四个阶段
真正能落到生产环境的一键脚本,不会从头到尾顺序执行,中间没有任何判断。我习惯把执行流程固定为四个阶段:探测、备份、执行、回滚。把这四段拆开写,有两个好处:第一,脚本的执行路径能被审阅,出了问题你能说出“它坏在哪一步”,而不是面对一个什么信息都不吐的黑匣子;第二,任何一个阶段失败,你都可以手工接管现场,而不是只能删掉整台机器重来。
2.1 先探测再动手:发行版、包管理器、内核与磁盘空间
很多脚本会把安装命令直接怼到最前面,这是设计错误。服务器环境的安装动作大多依赖包管理器,而 apt、dnf、zypper 的命令格式、软件包命名、源配置方式完全不同。脚本动任何系统级修改之前,至少要知道四件事:这是 Debian 系还是 RHEL 系;包管理器是什么;当前用户是不是 root;磁盘和内存够不够用。
这四件事里,发行版探测是后续所有命令的岔路口。常见做法是解析/etc/os-release,这个文件在几乎所有现代发行版上都存在,包括很多国产发行版。它的内容标准很统一,脚本里直接用 ID 字段做分支即可:
if [ ! -f /etc/os-release ]; then echo "无法识别的系统,脚本退出" exit 1 fi . /etc/os-release case "$ID" in debian|ubuntu) PKG_MGR="apt" ;; centos|rhel|rocky|almalinux|fedora) PKG_MGR="dnf" ;; kylin|uos) # 国产发行版多数兼容 apt,sl 系列再单独处理 PKG_MGR="apt" ;; *) echo "当前发行版 $ID 不在支持列表内" exit 1 ;; esac这里用. /etc/os-release而不是cat去抓文本,是因为 source 之后可以直接拿$ID、$VERSION_ID当变量用,后续判断版本号时也不用再解析一次。脚本开头遇到无法识别的系统就直接退出,比硬着头皮跑下去更安全。很多事故的根源不是命令写错,而是脚本跑在了作者根本没测试过的发行版上。
如果你维护的是几十台机器,我一般会在探测阶段顺便把内核版本也记录下来。uname -r一行命令而已,但等哪天真遇到内核模块加载失败,这个值就是排错的第一线索。磁盘空间检查同样不能省,我见过不少脚本装 MySQL 装到一半,日志目录直接写满,然后整个系统进入只读状态。检查方法很简单:
df -h / | tail -1如果根分区用量超过 85%,我倾向让脚本停下来,而不是赌安装过程不会继续吃磁盘。这种脏活不能让脚本自动替你决定,因为不同业务对磁盘余量的要求不一样,但脚本至少要把数字亮出来。
2.2 备份大于修复:在动手前给系统留一条退路
一键脚本写给新手用,最怕的是它把系统改坏了,新手又没有能力手动回滚。所以备份环节不是可选项。备份不必做到 btrfs 快照那种级别,你做安装和修复,要保护的其实就两类东西:一是原有配置,二是原有软件列表。
配置备份的常见做法是建一个带时间戳的目录,统一存放。任何即将被覆盖的配置文件,都先复制进去,路径结构保持原样,回头找的时候不费劲:
BACKUP_DIR="/root/backup/onekey-$(date +%F-%H%M)" mkdir -p "$BACKUP_DIR/etc" backup_file() { local src="$1" if [ -f "$src" ]; then cp -a "$src" "$BACKUP_DIR${src}" echo "已备份 $src 到 $BACKUP_DIR${src}" fi } backup_file /etc/nginx/nginx.conf backup_file /etc/mysql/my.cnf backup_file /etc/profilecp -a保留了权限、属主和时间戳,恢复的时候cp -a原路径拷回去即可,不会因为权限不对导致服务起不来。日期加时间这个格式有一个好处:同一天跑多次脚本,备份目录不会互相覆盖,每次都有独立的后悔药。
软件包列表是很多人忽略的一环。修复类脚本如果要把系统从半坏状态拉回来,安装了什么、删除了什么,脚本脚本要有记录。常见做法是执行前打一份快照,放在备份目录里:
- Debian 系用
dpkg --get-selections > "$BACKUP_DIR/pkg.list" - RHEL 系用
rpm -qa --qf '%{NAME}\n' | sort > "$BACKUP_DIR/pkg.list"
这份清单不一定用来精确回滚,因为有些包带了复杂的依赖关系,但在排查“哪个包被脚本动过”时,对比前后两份列表往往比翻日志快得多。回滚要用的不是一条命令,而是一个方向:如果新配置导致 nginx 起不来,先把备份目录里的原配置拷回去,再说其他。
2.3 写日志,不把 echo 当黑匣子
一键脚本最容易犯的毛病,是把输出全打在屏幕上。对交互式操作来说,这没问题,但脚本一旦放到后台跑,或者跑了十分钟后你才发现失败,没有日志文件就只能靠着记忆和猜测排障。
最朴素也最好用的日志方案,是把脚本的标准输出和标准错误同时送进文件。常见写法是:
LOG_FILE="/var/log/onekey-$(date +%F).log" exec > >(tee -a "$LOG_FILE") 2>&1这一行放在脚本靠前的位置,之后所有的echo、apt-get、dnf输出,会同时出现在终端和日志里。有人会直接写bash install.sh > install.log 2>&1,那样也能落盘,但用户看不到实时进度,像是在黑屋子里点蜡烛。tee两个都能满足,我一般默认用它。
日志路径建议固定,不要每次随机生成。常用的做法是/var/log/下按脚本名命名,比如/var/log/lnmp-onekey.log,方便事后 grep。再往后想一步,涉及长时间安装的任务,日志累积到几百兆也不奇怪,脚本结尾加一句保持最近若干个日志文件的小动作并不复杂:
find /var/log -name 'onekey-*.log' -mtime +7 -delete另外,这类脚本如果用 systemd 的 timer 或者 cron 触发,交互式终端根本不存在,此时 echo 打在哪根本不重要,日志文件反而成了唯一证据。所以我的习惯是:先落日志,再写功能。没有日志的一键脚本,我不认为它有资格给别人用。
3. 从零写一个 linux 环境安装脚本:最小可复现骨架与幂等设计
这一章给一个可以直接照着改的骨架。骨架的目标不是炫技,而是两个硬性要求:一,任何一次安装不依赖人工记忆,跑的时候没人盯着也能完成;二,同一份脚本在同一台机器上重复执行,结果不会越来越乱。第二点也就是常说的幂等性,它是一键脚本和平铺直叙的手工文档之间最本质的区别。
3.1 开头三件套:set -euo pipefail、root 检查和脚本目录定位
任何 bash 脚本,开头都会看到这三段几乎是行业共识的东西:
#!/usr/bin/env bash set -euo pipefail if [ "$EUID" -ne 0 ]; then echo "请用 root 或 sudo 运行这个脚本" exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly LOG_FILE="/var/log/onekey-install.log"set -euo pipefail三个选项干了三件事:-e让脚本在遇到任何非零返回值的命令时立刻退出,避免错误累积;-u让未定义变量的引用直接报错,这能拦住rm -rf $VAR/里 VAR 为空这类事故;pipefail让管道中的任一段失败都算失败,而不是只看最后一段。这套组合是脚本最基础的防护网,代价是写代码时必须重视退出码,那些你允许失败的命令,要显式加上|| true而不是赌它不出错。
EUID是 bash 提供的变量,root 用户的 EUID 恒为 0。安装环境类脚本必须用 root,是因为安装包、写/etc下的配置、启停 systemd 服务都需要这个身份。如果你的脚本本身就不是设计成 root-only,而是希望普通用户也能换源或做用户态配置,那这个检查不应该写成直接退出,而是尝试用sudo重新执行自己,但那样逻辑会更绕,默认建议还是 root-only。
SCRIPT_DIR这行是新手最容易抄错的地方。它取的来源是BASH_SOURCE[0],也就是当前脚本自身的路径,再用cd切换后pwd取回绝对路径。这样做的好处是:不管你在哪个目录下执行这个脚本,它都能正确找到自己同目录下的.env或其他辅助文件,不会因为pwd在当前目录就导致相对路径失效。
3.2 用函数做模块拆分:安装 Nginx 与 PHP 的最小骨架
一个往复杂里写的安装脚本,可以上千行。但不管多长,我都建议把每个软件包的安装过程 ko 成一个独立函数,主流程只做调度:
install_nginx() { echo "==> 安装 nginx" if ! command -v nginx >/dev/null 2>&1; then case "$PKG_MGR" in apt) apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y nginx ;; dnf) dnf install -y nginx ;; esac fi if ! systemctl is-active --quiet nginx; then systemctl enable --now nginx || { echo "nginx 启动失败,请检查 journalctl -u nginx" return 1 } fi } install_php() { echo "==> 安装 php-fpm" if ! command -v php-fpm >/dev/null 2>&1; then case "$PKG_MGR" in apt) DEBIAN_FRONTEND=noninteractive apt-get install -y php-fpm ;; dnf) dnf install -y php-fpm ;; esac fi if ! systemctl is-active --quiet php-fpm; then systemctl enable --now php-fpm || { echo "php-fpm 启动失败,请检查 journalctl -u php-fpm" return 1 } fi } main() { install_nginx install_php } main "$@"逻辑说明分成三点。第一,command -v nginx检测的是命令是否在 PATH 里,对确认“二进制已存在”够用,但对确认“服务可用”不够,所以后面又用systemctl is-active --quiet检查服务状态。有人会问,为什么不用if rpm -q nginx或dpkg -l去查包?因为包和命令之间不是严格的一一对应,同一个 nginx 在 Ubuntu 上可能被拆成 nginx-core、nginx-common 好几个包,直接查命令更可靠。
第二,DEBIAN_FRONTEND=noninteractive是给 apt 用的环境变量,它阻止安装过程中弹交互式配置界面。在无人值守脚本里,这一步不做,apt 可能在等待时挂着,屏幕上看起来一切正常,实际早已卡死。第三,启动失败时函数返回 1 而不是exit 1,是为了让主流程可以选择“跳过这个模块继续其他修复”,而不是整个脚本直接终止。具体怎么选,取决于脚本定位:安装类脚本我倾向失败就停,修复类脚本允许跳过。
3.3 包管理器的三个可选动作:更新索引、自动确认、清理缓存
如果你要在脚本里做批量安装,不要每个安装动作都单独写一遍 apt-get 或 dnf。把安装逻辑包成一个函数,顺便把三个通用动作统一处理掉:
install_pkgs() { local pkgs=("$@") case "$PKG_MGR" in apt) apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}" apt-get clean ;; dnf) dnf makecache --refresh dnf install -y "${pkgs[@]}" dnf clean all ;; esac } install_pkgs curl unzip wgetapt-get update的作用是刷新软件源索引,新装的系统或更换过源的机器不执行这一步,很可能出现“软件包列表为空”或 404。-qq表示安静但保留错误,比-q更彻底地减少无意义输出。-y参数则直接对所有询问回答 Yes,否则 apt 升级到一半突然问“是否继续”,脚本就停在那了。
dnf 侧对应的刷新指令是makecache --refresh,功能类似但不完全等价。最后的clean动作用于清理下载的包缓存,apt 的缓存放在/var/cache/apt/archives,dnf 的在/var/cache/dnf。对磁盘小于 40G 的云主机,这个动作能省出好几个 GB,而且不会影响已安装的软件。
函数化安装还有一个作用:统一入口之后,后续想加“安装前打日志”“安装后验证版本”,只需改一处。把包名放在函数参数里,而不是写死在函数内部,是为了让调用处一眼能看到这轮安装装了什么,减少阅读成本。
3.4 配置覆盖前先做 diff:不让重复执行变成配置漂移
环境安装脚本往往伴随配置修改,最常见的翻车点是它不管三七二十一,一上来就把整个nginx.conf覆盖掉。如果这是全新服务器,问题不大;如果是在一台跑着业务的机器上做“修复”,这就是事故。覆盖前的 diff 是最后的刹车:
backup_and_replace() { local src="$1" local dst="$2" if [ -f "$dst" ]; then cp -a "$dst" "$dst.bak.$(date +%F-%H%M)" fi if ! diff -q "$src" "$dst" >/dev/null 2>&1; then cp -a "$src" "$dst" echo "配置已更新: $dst" else echo "配置无变化,跳过写入: $dst" fi }diff -q只输出“文件是否相同”这一个结论,>/dev/null把输出丢掉,我们只消费它的退出码。相同则什么都不做,不同才备份并覆盖。为什么要让重复跑脚本的结果保持一致?安装脚本通常会被反复执行,不幂等的脚本每次执行都会留下细微差异,时间长了就变成“跑一次能用,跑三次才出问题”。这和“玄学”故障往往就是从这里逐步积累的。
还有一个隐藏好处是避免无谓的服务重启。如果配置内容没变,就不需要触发systemctl reload nginx。可是有的脚本实现里,它不管 diff 结果,每次都执行 reload,生产环境流量波动时这种多余操作很容易变成线上事故的诱因。
4. 服务器环境安装脚本的参数设计:内存、并发与版本选择的平衡点
脚本跑通不是难点,跑完之后服务在两三个请求后不 OOM、不在日志里刷满 “too many open files”,这才见功力。很多一键脚本默认参数是在 8C16G 的测试机上调出来的,照搬到 1G 内存的小主机上,效果天差地别。这一章把几个真正决定上层应用稳定性的参数拿出来说清楚。
4.1 必调参数:PHP 版本、MySQL 密码、nginx worker_processes
安装脚本里不要出现散落在函数深处的魔法数字和魔法字符串。常见做法是文件头部集中定义参数,并支持外部.env覆盖默认值:
ENV_PHP_VERSION="8.1" ENV_MYSQL_PORT="3306" ENV_MYSQL_ROOT_PASSWORD="$(openssl rand -base64 18)" ENV_NGINX_WORKERS="$(nproc)" ENV_WEB_ROOT="/var/www/html" if [ -f "$SCRIPT_DIR/.env" ]; then . "$SCRIPT_DIR/.env" fi这里有两个细节值得展开。第一,ENV_MYSQL_ROOT_PASSWORD用了openssl rand -base64 18自动生成,而不是写死一个弱密码。这样脚本可以无人值守安装,同时避免所有机器用同一个密码的安全隐患。生成的密码会在后面的日志里输出一次,然后建议把存放路径和权限收紧。第二,ENV_NGINX_WORKERS="$(nproc)"把 worker 进程数绑定到 CPU 核数,比手动填一个数字更不容易过时。需要注意,$(nproc)在容器环境中返回的是宿主机的核数,如果你在容器里跑,这是典型的坑,后面讲参数表时单独说。
常见的参数及调整时机,整理如下:
| 参数 | 参考默认值 | 什么时候需要改 |
|---|---|---|
nginxworker_processes | auto或$(nproc) | 容器限制 CPU 时改为实际配额,例如worker_processes 2 |
nginxworker_connections | 1024 | 单机并发高或压测出现 socket 耗尽时调大到 4096 |
PHPmemory_limit | 128M | WordPress、图片处理任务跑到一半超时,建议调到 256M |
MySQLinnodb_buffer_pool_size | 物理内存 50%,且不超过 1G 上限 | 512M 内存小机器必须往下压,否则系统 OOM 风险极高 |
这些参数写进脚本前要问一句:这台机器是给人练手的,承载的是开发环境,还是真要接流量的生产机?答案不同,参数几乎要砍一半。脚本建议默认值按低配走,并将高配参数写进注释,而不是反过来。
4.2 小内存场景的 3 个隐藏开关:swap、fpm 进程数与开放连接数
1G 内存的云主机是 Linux 环境安装脚本最常面对的场景之一,三个隐藏开关决定它能不能撑住 MySQL 和 PHP-FPM。第一个是 swap。没有 swap 的机器,内存一吃紧,内核 OOM Killer 会直接挑一个进程杀掉,通常是 MySQL。脚本里可以自动创建 1G 的 swap 文件:
if [ ! -f /swapfile ]; then fallocate -l 1G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile echo '/swapfile none swap sw 0 0' >> /etc/fstab fichmod 600这条必须紧跟,否则系统会拒绝使用权限过宽的 swap 文件,这也是一键脚本里最容易漏的一步。第二个是 PHP-FPM 的进程池数量,默认配置常见pm.max_children=20,对 1G 内存机器意味着可能有 20 个 php-fpm 进程同时驻留内存,每个吃掉几十 MB,很快就被拖垮。调整为pm.max_children=5、pm.start_servers=2更加现实。第三个是进程能打开的文件数,高并发场景下 nginx 和 php-fpm 都可能碰too many open files的硬顶。脚本里追加一个 ulimit 配置,写进/etc/security/limits.d/下,按服务用户单独设置,比全局调低内核参数更克制。
4.3 安装顺序与二进制包选型:为什么你该依赖发行版仓库而不是源码编译
安装类脚本还要决定一件事:软件包从哪里来。我知道部分开发者偏好源码编译安装,理由是可定制、性能优。但在一键脚本里,我的建议是优先使用发行版官方仓库的二进制包。原因很实际:包管理器会负责依赖,出问题时apt-get remove一条命令能干净卸载;源码编译则需要手动管理中间产物、头文件和对某些库的隐性依赖,脚本排错时成本大很多。常用做法是先用apt-cache policy php8.1或dnf info nginx确认发行版仓库里的版本,如果版本太旧,再考虑第三方仓库或源码安装,而不是一开始就编译。
安装顺序的常见约定是数据库先行,Web 服务和 PHP 靠后。原因是 PHP 扩展比如pdo_mysql,编译或安装阶段可能依赖 MySQL 客户端头文件,虽然二进制包场景下这个约束弱一些,但顺序固定下来,遇到问题是能减少一个变量。更重要的顺序其实是“配置生成在后”。先把软件装上,再写配置,再启动服务,不要在软件还没就位时就急着把配置覆盖进去。
5. 避坑:linux 一键脚本最容易翻车的 6 个现场
踩坑记录比功能函数值钱。以下几个场景来自运维一线,按“现象到原因再到解决”的方式拆开,它们也是我给一键脚本做 review 时重点盯的位置。
5.1 在 Ubuntu 上执行 yum 安装:发行版判断写死
现象:脚本写的是yum install -y nginx,拿到 Ubuntu 机器上跑,报yum: command not found。或者更隐蔽一点,某些基于 Ubuntu 的镜像里装了 yum 兼容层,结果装了一堆不该出现的 RPM 包,把系统依赖搅乱。原因:写脚本的人默认“服务器环境 = CentOS”,没有用/etc/os-release做运行时探测,发行版判断写死成了固定值。解决:任何涉及到包管理的命令,先走一遍发行版映射,务必要在脚本运行时判断,而不是安装前判断一次就完事。上面 2.1 节和 3.3 节里的函数就是为此存在的。
5.2 换源时没有连通性检测:整台机器从此装不了包
现象:修复类脚本为了加速,把系统源地址替换成某个镜像站,执行完成后发现后续所有apt-get update都超时或 404,比不换源还糟。原因:替换之前没验证目标源是否可达,也没验证源里配置的版本代号(比如bookworm或jammy)是不是和当前系统匹配。解决:替换前先备份,替换后用curl -I --max-time 5探活,并将探活和 update 测试放进脚本里,失败即刻回滚备份。换源脚本比安装类脚本更需要回滚能力,因为它直接破坏系统的包管理基础。
5.3 脚本跑到一半 SSH 断开:没有日志和断点标记
现象:一个安装脚本预计跑 20 分钟,SSH 连接中断,重连后系统处于半安装状态:nginx 装好了,MySQL 没装完,脚本不知道下一步该做什么。原因:没有用nohup把脚本和终端会话解绑,也没有做阶段标记。解决:执行时用
nohup bash install.sh > install.log 2>&1 &把任务丢到后台。脚本内部用标记文件做断点,例如在安装 MySQL 成功后执行touch /root/.onekey-mysql-done,下次运行时先检查标记,已完成的步骤直接跳过。这里要注意,重复执行的路径必须一致,标记文件里写明完整路径,否则第二次用相对路径启动,标记文件找不到,又会走到错误的恢复逻辑里。
5.4 MySQL 本地能连但程序连不上:socket 和 TCP 混在一起
现象:安装完成后,mysql -uroot -p在命令行里能登入,PHP 程序却报Can't connect to MySQL server on '127.0.0.1'。原因:MySQL 在本地默认走的是 unix socket,而程序通过 TCP 去连127.0.0.1:3306,要么 mysqld 没有监听 TCP 端口,要么 bind-address 只绑了::1,要么端口被防火墙拦截。解决:脚本安装完成后,自动执行一次端到端检查,命令是ss -ltnp | grep 3306 || true,确认监听的地址,再通知 PHP 端使用正确的 host 配置。这个坑的本质是localhost和127.0.0.1在 MySQL 语境下并不等价,脚本里尽量显式写 IP,避免含糊。
5.5 启动 nginx 被占用端口打断:缺少重启前的预检
现象:systemctl start nginx报Job for nginx.service failed,旁边还有一个 Apache 或另一个 nginx 在占着 80 端口,脚本没有提示就直接失败了。原因:没有在启动服务前检查端口占用。解决:加预检动作:
if ss -ltnp | grep -q ':80 '; then echo "80 端口已被占用,启动 nginx 前请先处理" return 1 fi或者提供更温和的降级策略,将 nginx 默认端口改为 8080 再启动。一键脚本最容易忽略的就是这类“环境里还跑着别的服务”的假设。预设全新服务器可以用强校验,预设修复已有机器则要留出容错空间。
5.6 rm -rf 出现在修复脚本里:变量为空时的反向破坏
现象:脚本作者本意是清理临时文件,一条rm -rf $TMP_DIR/*因为变量为空变成了rm -rf /*,系统整机瘫痪。原因:set -u没有启用,而且对路径变量没有判空。解决:删除前先确认路径可信:
if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then find "$TMP_DIR" -type f -delete else echo "TMP_DIR 为空或不存在,跳过删除" fi用find ... -delete代替rm -rf,也是一个更克制的习惯。find能列出准备删除的文件,还能配合-mtime限制只清理特定时间的文件。这点上我对自己的要求是:能不用 rm 就不用 rm,删除操作永远是脚本里风险最高、最需要被显式注释和确认的代码段。
6. 跑完脚本后的 10 分钟验收:健康检查、增量更新和两个使用习惯
一键脚本的价值不在于装上那一刻,而在于安装后还能持续地被人信任和使用。每个脚本跑完,我都会花 10 分钟做一次验收,内容固定,基本是一条流程走到底。
6.1 用一条命令验整体状态
check_active() { for svc in "$@"; do if systemctl is-active --quiet "$svc"; then echo "OK $svc" else echo "FAIL $svc" fi done } check_active nginx php-fpm mysql curl -sS -o /dev/null -w "HTTP Code: %{http_code}\n" http://127.0.0.1/ || truesystemctl is-active --quiet只返回退出码,不输出多余内容,适合做状态巡检。curl 的-w参数用于输出响应码。这里故意加了一个|| true,因为健康检查失败时不应该中断整个验收流程,而是把所有失败项集中列出来,最后一起处理。MySQL 侧再顺手执行一句mysqladmin ping -h 127.0.0.1 -P 3306 -u root -p"$ENV_MYSQL_ROOT_PASSWORD",能连通基本就说明 socket 和端口都没问题。
6.2 增量更新与版本标记:让脚本不再是一次性消耗品
脚本交付后不是终点,环境会变化,软件的版本也会沉淀在脚本里。我的习惯是把所有脚本纳入版本管理,每次改动在文件头部加一行版本注释,例如# v1.2.0 - 增加 Ubuntu 24.04 支持,同时约定日期和修改人。这样执行脚本前先diff一下代码和上次有没有不同,再决定要不要跑,至少能避免“拿一本三个月的旧脚本去修现在的系统”。
还有个更实用的技巧:把参数文件从脚本里拆出来。脚本主体只做逻辑,所有和环境相关的配置放.env,不同环境用不同的.env去引用同一份脚本。增量更新时只更新脚本或只改配置,互不打扰,回滚时也只要把.env换回上一版。这套做法我用了很长时间,它解决的最大问题就是老话说的“改一处,崩全局”。
我给自己定的规矩是:一个一键脚本必须能在干净的测试机上连跑三遍而不产生差异,才敢把它放进生产服务器。这个习惯救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取