一、起因:巡检报告红了
每天 14:00,Hermes 的巡检脚本都会准时给我推一份服务器体检报告。这天报告一到,跟以往不一样了:
🖥️ 【甲骨文ARM服务器安全巡检报告】
• 磁盘使用率: 85.1% (已用 82.3G / 总共 96.7G)
• 内存使用率: 46.7% (可用 6.2G)
• CPU 负载(1m): 0.14
• 域名证书(boke.xunda.nyc.mn): 剩余 85 天
⚠️ 警告: 磁盘空间告急!
状态:【需要关注 🔴】
[[IMG1]]
我先还犯了个嘀咕:拿 FinalShell 的面板一对照,两边数字”对不上”?仔细一看,其实对得上——FinalShell 显示根分区可用 13.8G/96.7G,跟巡检的 85.1% 是一回事。真正扎眼的是时间线:9 天前的巡检还是 74.1%,涨了 10.6G。 这么涨下去,十天半个月就得满盘。
问题来了:这 10 个 G,被谁吃了?
[[IMG2]]
二、一层一层往下挖
在宿主机 root 下,先看五大目录的体量:
du -sh /backup /www /root /var /opt 2>/dev/null | sort -rh | head
31G /www
18G /var
11G /backup
8.7G /root
7.4G /opt
/www 31G 最肥,往下钻:
du -sh /www/* /var/* 2>/dev/null | sort -rh | head -15
22G /www/backup
11G /var/lib
7.6G /www/server
4.9G /var/log
2.5G /var/cache
632M /www/wwwlogs
367M /www/wwwroot
注意这个反差:站点本体 /www/wwwroot 才 367M,备份目录 /www/backup 却堆了 22G。备份比本体大 60 倍,这里面必有故事。
再钻两层,目标锁定:
22G /www/backup/site
...
21G /www/backup/site/moov.xunda.nyc.mn
ls -lhS /www/backup/site/moov.xunda.nyc.mn/
7.0G web_moov.xunda.nyc.mn_20261019_013017_p142Q6.tar.gz
7.0G web_moov.xunda.nyc.mn_20261012_013015_RjkX8X.tar.gz
7.0G web_moov.xunda.nyc.mn_20261005_013016_6nCCyY.tar.gz
每周一凌晨 01:30,宝塔把电影站整个打成 7G 的包,攒了三份就是 21G。
止血第一步:删掉两份旧包,保留最新一份(另一份每日灾备脚本还在兜底):
rm /www/backup/site/moov.xunda.nyc.mn/web_moov.xunda.nyc.mn_20261005_013016_6nCCyY.tar.gz \
/www/backup/site/moov.xunda.nyc.mn/web_moov.xunda.nyc.mn_20261012_013015_RjkX8X.tar.gz && df -h /
/dev/sda1 97G 70G 28G 72% /
85% → 72%,腾出 14G。但这只是把最肥的割了,病根还没除:每周一它还会再长 7G 出来。
三、第一道坎:站点本体 367M,7G 的备份包到底打的是什么?
按理说 moov 站应该住在 /www/wwwroot 里,可 wwwroot 总共才 367M。它的备份包凭什么 7G?查 nginx 配置:
grep -E '^\s*root' /www/server/panel/vhost/nginx/moov.xunda.nyc.mn.conf
root /opt/maccms/maccms10-master;
站点根目录压根不在 wwwroot,在 /opt/maccms——苹果CMS(maccms)影视站。继续挖:
du -sh /opt/maccms/maccms10-master/* 2>/dev/null | sort -rh | head -8
7.4G /opt/maccms/maccms10-master/upload
13M static_new
7.8M static
6.8M application
...
程序本体加起来才 50M,7.4G 全在 upload 里。再往下:
find /opt/maccms/maccms10-master/upload/vod -type f | wc -l
164681
16.4 万个文件,平均每个 45KB,全是海报图(jpg 占 15.8 万)。按月份一数,吓一跳:
154483 2026-01 ← 一个月灌进来 15.4 万张
978 2026-02
1850 2026-03
...(之后每月几百到一千多)
2026 年 1 月做过一次批量采集,一次性砸进来 15.4 万张图。备份包 7G ≈ upload 7.4G,谜底对上了:周备打的是整个 /opt/maccms,大头就是这批图。
四、第二道坎(最险的一次):一个前导斜杠,差点让我删光在用的图
16 万张图,多少还在用?我的思路是:问数据库——苹果CMS 的 mac_vod 表用 vod_pic 等字段存图片路径,查一下引用不就知道了?
结果连着三次查询都告诉我:“本地图片几乎没人引用”——按 2026-01 批次查,只有 2 条引用;按月份分布查,全库只有约 105 条本地引用;最后做文件名匹配,得到一个更惊悚的数字:
== DB引用的唯一文件名数: 105
== 磁盘文件: 164662 | 被引用: 0 | 孤儿: 164662
被引用 0。 按这个结论,16.4 万张图全能删。我当时方案都拟好了,只差敲回车。
幸好立了条规矩:删除之前,先把样例打出来看一眼。这一看,看出问题了:
== vod_pic 样例
upload/vod/20261020-1/1a4de4f6f5d9df766014a368b657142f.jpg
路径是 upload/vod/...——开头没有斜杠!而我前面所有查询用的匹配条件是 LIKE '%/upload/vod/%',模式里带前导斜杠。upload/vod/ 前面没有那个 /,自然一条都匹配不上。
一个字符的差别,三次查询给出三种”看似合理”的错误答案。要是当时直接信了”被引用 0″,一键下去,4.4 万张正在使用的海报当场全灭,电影站前台图裂一片。
改掉条件重跑,真实数字出来了:
== DB引用的唯一文件名数: 43994
== 磁盘文件: 164662 | 被引用: 43889 | 孤儿: 120773
== 样例文件在磁盘上: True
4.4 万张在用,12 万张孤儿,5.64G。
五、第三道坎:全库扫描,又冒出一张附件表
删之前做最后一次引用面排查——不只查 mac_vod 的 4 个图片字段,把 27 张表的所有文本字段全扫一遍:
== 全库引用 upload/vod 的字段:
('mac_annex', 'annex_file', 160621)
('mac_vod', 'vod_pic', 43994)
冒出个 mac_annex(附件表),16 万条记录几乎把每个文件都登记了一遍。这表是干嘛的?会不会有前台在用?
顺着代码查引用:
grep -rln 'annex' /opt/maccms/maccms10-master/application --include='*.php' | grep -v '/lang/'
application/common/validate/Annex.php
application/common/model/Image.php
application/common/model/Annex.php
application/common/model/Upload.php
application/admin/controller/Annex.php ← 只有后台管理控制器
application/admin/common/auth.php
只有后台的文件管理在用。结论:删文件的同时,把附件表里失效的记录一并清掉,后台才不会留下一堆指向空气的死条目。
六、动手:先备份,再三步清理
执行顺序是”备份 → 删文件 → 清空目录 → 清数据库记录 → 对账”,任何一步出错立即中止:
1. 附件表全量备份(14MB SQL,可随时还原)
2. 删孤儿文件:只删”不在数据库引用集里”的,且 24 小时内新文件跳过(防止采集程序正在写入的被误伤);删了什么,全部记进日志文件
3. 清掉留下的 116 个空目录
4. 清附件表:判断标准不是”在不在引用集里”,而是更硬的一条——磁盘上这个文件还在不在
最终对账:
1) DB引用文件名数: 43994
2) 附件表已备份 → /root/mac_annex_backup_20261020.sql (14 MB)
3) 删孤儿文件 120773 个,释放 5.64 GB | 保留 43889 | 24h内新文件跳过 0 | 失败 0
4) 清理空目录 116 个
5) 附件表:扫描 116732 条失效记录,已删 116732 条
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 97G 64G 34G 66% /
数字自己会对上:附件表原有 160,621 条,删掉 116,732 条,剩 43,889 条——跟保留的文件数一个不差。
外部验证三连(不登服务器,直接从公网访问):
首页 HTTP 200 (0.333s) ← 站点正常
样例保留图 HTTP 200 image/webp 22322B ← 在用的图完好
已删孤儿图 HTTP 404 ← 孤儿确实清干净了
七、止血:让备份不再每周长 7G
清理只管现在,宝塔每周一 01:30 还会再打 7G 的包。查它的计划任务,发现保留份数是 3:
(2, 'Backup Site[ALL]', 'week', '1', 1, 30, ... save=3 ...)
关键细节:保留份数存在两个地方——脚本里硬编码了一份,数据库里一份:
cat /www/server/cron/5948aab79b0221d83f5d5ff9cbca2fd5
# 里面:
/www/server/panel/pyenv/bin/python3 -u /www/server/panel/script/backup.py site ALL 3 5948aab79b...
只改数据库、不改脚本,等于白改(脚本跑的还是 3);只改脚本,下次在面板里编辑任务又会被写回 3。两处一起改成 1:
sed -i 's#backup.py site ALL 3 #backup.py site ALL 1 #' /www/server/cron/5948aab79b0221d83f5d5ff9cbca2fd5
# 再把 default.db 的 crontab 表 id=2 记录 save 字段改为 1
改完验证两处都是 1,从此备份区封顶约 7.5G,不再滚动膨胀。
八、战果
[[TABLE1]]
留了三个安全网在宿主机 /root 下,观察一周没问题再删:
mac_annex_backup_20261020.sql— 附件表全量备份maccms_orphan_files_deleted_20261020.log— 被删的 12 万个文件清单maccms_annex_deleted_ids_20261020.txt— 被删的附件记录 ID
九、避坑总结
1. 备份比本体大几十倍,必有妖——du 从顶层一层层钻,别猜,每层一条命令让数字自己说话。
2. “备份”不等于”安全”——宝塔周备和每日灾备挤在同一块盘上,真碰上盘损谁也救不了谁;保留份数是隐藏的膨胀阀,装完面板先查它。
3. 删文件前必须过”数据库引用集”这一关,而且引用集的匹配条件要拿真实样例验证——这次一个前导斜杠的 LIKE 差点让我误删 4.4 万张在用图。先核验、拿样例、再动手,这条规矩救了整个站。
4. 隐藏引用面要全库扫:别只查主表那几个字段,information_schema 一把梭把 27 张表全过一遍,才能把附件表这种”半路杀出的程咬金”逼出来。
5. 删完必须外部验证:首页 200、在用资源 200、已删资源 404——三个数字都对,才算真删干净。
6. 改配置要找全它的所有副本(脚本+数据库各一份),漏一处,改了也白改。

发表回复