服务器迁移与日常运维脚本集。**原机器彻底用不了时,看 灾难恢复手册。**环境相关的值通过本机 /etc/ops-scripts/env.conf 配置,仓库只保存 配置模板;真实域名、IP 和凭据不进入公开仓库。init/、vpsscore/ 等自包含脚本可在没有配置文件的新机上运行。
# 1. 装引导器
curl -fsSL https://raw.githubusercontent.com/MAXLYEN/ops-scripts/main/bin/opsget \
-o /usr/local/bin/opsget && chmod +x /usr/local/bin/opsget
# 2. 打开中文菜单(首次会自动安装),按分类或「按场景找」挑功能
opsget # 之后也可以直接敲 opsbox
# 或者用命令行:按已安装脚本的需求生成或补齐配置
opsget -c # 只补当前需要的配置键
vi /etc/ops-scripts/env.conf
# 3. 用
opsget -l # 列出可用脚本
opsget migrate/01-inventory # 拉取并执行
opsget -i ops/preflight-backup # 只安装到 /usr/local/bin,不执行
# 4. 生产机固定到验证过的版本(见「固定版本」)
opsget --pin v2026.09.24 && opsget -u路径写成 migrate/03-pre-migrate 或 migrate/03-pre-migrate.sh 都可以,参数直接跟在后面:opsget ops/cleanup-tidy --apply。
菜单 opsbox 给每个功能写了「什么时候用、会做什么」,改动类操作先预演再确认,常用组合(备份体检、升级前自动备份、改防火墙前自动快照等)自动串联;它背后调用的仍是 opsget,遵守同一个固定版本。哪些脚本进了菜单、哪些没进及原因,见 bin/README.md。
注意 -i 是 opsget 自己的选项,要写在路径前面。opsget ops/ssl-audit -i 会把 -i 当成传给脚本的参数,照常执行。
看 MANIFEST,或在机器上跑 opsget -l。 这两处列出通过引导器展示的脚本;目录介绍包含该目录全部文件的作用与版本。
这里不再重复列表——两处维护必然漂移,实测过:README 的编号和实际差了一位,两个脚本改过名,整个 vpsscore/ 目录漏掉了,照 README 敲会 404。
| 目录 | 内容 | 目录文档 |
|---|---|---|
bin/ |
脚本引导器 | 介绍 · 版本记录 |
config/ |
环境配置模板 | 介绍 · 版本记录 |
lib/ |
公共函数库 | 介绍 · 版本记录 |
init/ |
新机初始化 | 介绍 · 版本记录 |
migrate/ |
整机迁移流程 | 介绍 · 版本记录 |
backup/ |
生产备份 | 介绍 · 版本记录 |
ops/ |
日常运维 | 介绍 · 版本记录 |
db/ |
数据库维护 | 介绍 · 版本记录 |
vpsscore/ |
VPS 质量采集与评分 | 介绍 · 版本记录 |
openclash/ |
OpenClash DNS 分流 | 介绍 · 版本记录 |
tests/ |
静态检查与端到端测试 | 介绍 |
docs/ |
灾难恢复手册:原机已不在时从零恢复、唯一要手动保存的密码、演练 | 灾难恢复 |
init/ 与 vpsscore/ 不依赖 lib/common.sh,方便在新机上单独运行,也不强制要求 env.conf。ops/decommission-archive 同样自包含,以适应机器即将退役的场景。
新机初始化:00 →(需要则重启)→ 01 → 02 → 03 →(重启)→ 04
opsget init/run # 列出阶段与当前状态
opsget init/run 03 # 执行指定阶段跑 03 之前务必确认带外控制台能进。 脚本布置了 5 分钟自动回滚兜底,但那是最后一道保险,不是第一道。调度器在 03 之前也会强制确认第二个 SSH 窗口已就绪。
整机迁移:原机还在时,在旧机上跑一键迁移 opsget migrate/live-migrate <新机IP>(预检 → 演练 → 停写切换 → DNS,DNS 切换前可回滚);原机已经没了看 灾难恢复手册;服务不在备份包里的机器走 migrate/ 下按编号的旧流程。怎么选、各步做什么见 迁移目录介绍。
真实配置在 /etc/ops-scripts/env.conf,权限 600,永远不进仓库(.gitignore 已排除)。
需要配置的脚本在头部声明 # ENV-REQUIRED:;opsget -e <路径> 可查看所需和缺失的键,opsget -c <路径> 可按需补齐。声明为 A|B 时任一有值即可。对这些必填项,缺失时脚本会报错退出,避免用错值静默执行。
DOMAINS 和 XBOARD_SITES 留空时会自动扫描 vhost 目录,面板上增删域名不用回来改配置。
填了则按列表走,并与实际情况双向比对后告警:
- 列了但 vhost 里没有 → 站点已删,配置该清理(噪音)
- vhost 里有但没列 → 这些站点不会被检查(致命)
第二种才是真问题:漏掉的站点压根不进循环,输出还是全绿,看起来像"检查过了"。实测踩过——证书审计里一个已删的域名报了半年假警,而真在跑的站从来没被检查过。
同理,凡是"要人记得同步"的清单,时间足够长就一定会不同步。新增这类配置时优先做成"留空 = 自动发现"。
cron 永远调用本地已安装的脚本,绝不调用 opsget。
# 对
15 * * * * /usr/bin/flock -n /var/lock/vault-backup-cron.lock /usr/local/bin/vault-backup.sh >> /var/log/vault-backup-cron.log 2>&1
# 错
15 * * * * opsget backup/vault-backup
三个理由:
- 不能让备份依赖外网才能启动。 凌晨三点半 GitHub 连不上,备份就静默不跑了 —— 而备份失败恰恰是最难发现的一类故障
- 不能让未经验证的代码在无人值守时自动生效。 云端改一行,当晚就跑在生产备份上,没人看着
- opsget 每次更新都会留
.bak,cron 频率下会堆积
所以云端是分发源,本地文件才是运行的东西,更新是一个显式动作:
opsget -i <路径> # 只安装,不执行
/usr/local/bin/<脚本> # 手动跑一次验证
# 确认没问题,下次 cron 自然用新版安装路径与 cron 里写的路径一致,所以更新脚本不需要动 crontab。
cron 只调用本地脚本,挡住的是「云端改动在无人值守时生效」;但下一次有人手动 opsget,main 上最新的提交照样会装上来。main 上一个坏提交,所有机器下次更新都会中招 —— vw-fullbackup 2.3.4 就是这样把生产备份弄坏的。
生产机固定到验证过的 tag:
opsget --pin v2026.09.24 # 之后 -i / -u / -l / 执行都只从这个 tag 拉
opsget -u # 引导器、common.sh 和菜单也换成这一版
opsget --pin # 查看当前 ref 与来源
opsget --unpin # 取消固定,回到 main固定写在 /etc/ops-scripts/ref。环境变量 OPS_REF 优先于它,用于单次覆盖;opsget 执行脚本时会导出 OPS_REF,common.sh 的 ops_base() 按同样顺序判定,所以 script-inventory 等脚本比对的是同一个版本。
发版流程:
- 改动推到
main,CI 通过 - 挑一台机器按完整提交号单次覆盖装上新版并手动跑一次:
OPS_REF=<40 位提交号> opsget -i <路径>(common.sh会一并换成该提交的版本)。不要用OPS_REF=main:raw.githubusercontent 会把分支指向的提交缓存几分钟,查询参数绕不过,刚推送后拉到的可能还是旧版,验证就白做了 - 验证通过后打 tag 并推送:
git tag -a v2026.09.24 -m "<说明>" && git push origin v2026.09.24,同一天再发用v2026.09.24.1 - 其余机器
opsget --pin <新 tag> && opsget -u,需要更新的脚本再逐个opsget -i
opsget -u 只换引导器、common.sh 和菜单,已装的脚本不跟着变。回滚也一样:opsget --pin <旧 tag> && opsget -u 之后,这一版改过的脚本要逐个 opsget -i 装回旧版(新版脚本可能调用旧 common.sh 里没有的函数),最后 opsget --outdated 应显示全部一致。
tag 发布后不要移动,有问题就打新 tag。--pin 会拒绝 main、拒绝仓库里取不到的 ref,也拒绝 opsget 早于 1.4.0 的 ref —— 固定到那里再 opsget -u,会装回一个不读固定文件的旧引导器,固定就悄悄失效了。
opsget 从 1.2.1 起给每次请求加了 cache-buster。原因是 raw.githubusercontent 有约 5 分钟 CDN 缓存,而"改完立刻验证"恰恰是最常见的场景——拿到旧版会让人误判成"改了没效果",然后去改本来正确的代码。
如果你的 opsget 还是 1.2.0,症状是:提交了新版,重跑却显示 [=] 已是最新、common.sh 已更新 一行都没有。要么等几分钟,要么自己带参数绕过:
curl -fsSL "https://raw.githubusercontent.com/MAXLYEN/ops-scripts/main/bin/opsget?nc=$(date +%s)" \
-o /tmp/opsget.new && grep -m1 '^# VERSION' /tmp/opsget.new固定了版本的机器拉不到 main 上的新提交,这是设计如此;验证新提交用 OPS_REF=<完整提交号> 单次覆盖(分支名有几分钟缓存,见上)。
common.sh 不用单独安装——每次执行任何脚本时 sync_lib 都会同步一次。改了 common.sh 就等于所有脚本都拿到了新版,这也意味着改它要格外小心。
两个脚本都默认只列不删,加 --apply 才执行。
opsget ops/cleanup-tidy # 预演:会删哪些冗余
opsget ops/cleanup-tidy --apply # 执行,每类保留最新 2 份
KEEP=5 opsget ops/cleanup-tidy --apply
opsget ops/cleanup-purge # 预演:彻底移除会删什么
opsget ops/cleanup-purge --apply # 执行,还需输入 yes 二次确认
KEEP_ENV=1 opsget ops/cleanup-purge --apply # 保留 env.confcleanup-purge 按 /var/lib/ops-scripts/installed.list 台账精确回收,不会误删同目录下你自己的脚本。两个脚本都对以下路径做了硬拦截,任何情况下都不删:
BACKUP_DIRS备份产物CONTAINER_DATA_DIRS容器数据- 凭据文件(
BACKUP_PASS_FILES、MYSQL_DEFAULTS_FILE、rclone 配置、msmtprc) PANEL_ROOT面板目录- 云端的任何文件
注意这份保护名单是从配置拼出来的。 新增了备份目录却忘了写进 BACKUP_DIRS,硬拦截就不覆盖它——前面那些漂移是"少检查了",这个漂移是"少保护了"。改 BACKUP_DIRS / CONTAINER_DATA_DIRS 时想一想这件事。
仓库采用以下通用约定,具体以各脚本的实现为准:
- 幂等安装:写入前比对,内容相同直接执行,不同则备份旧版后替换
- 危险操作需确认,且尽量提供回滚路径
- 凭据永不出现在命令行,一律从
600权限的文件或 stdin 读。例外是 7z:它没有从文件读密码的选项,备份相关脚本仍以-p传入。由init/02以hidepid挂载/proc兜底 —— 普通用户看不到其他用户的进程与命令行;老机器按init/README.md手动启用 - 日志时间戳在调用时计算,不用启动时冻结的变量
set -o pipefail,管道错误不吞- 告警计数要值钱:正常配置引发的提示用
log不用warn。如果"1 条告警"永远消不掉,很快就没人看这个计数了 - 每个脚本头部保留作用、
# VERSION:、本版改动原因和必要的运行约束;旧版本的详细说明写入所在目录的CHANGELOG.md - 脚本正文保留解释关键判断和回滚条件的注释;会写入目标配置文件的注释属于输出内容,不能随意删除
- 各脚本独立编号;修改时递增相应版本,并同步目录版本记录
仓库公开,所以:
- ❌ 不放任何密码、密钥、token(包括心跳 URL —— 拿到就能伪造心跳)
- ❌ 不放域名、IP、邮箱、容器名、库名
- ❌ 不放运维文档(文档含真实拓扑,另行私有保存)
- ✅ 只放通用逻辑
如果你要新增脚本,问自己一句:把这个文件贴到公开网页上,会泄露什么? 答案必须是"只有我的运维习惯"。