Root cause analysis of the 2026-07-20 redundancy incident: 1. No single-source-of-truth rule -> same file legitimately lived in 4+ locations, diverging silently 2. Relative path resolution (Path(__file__).parent/'data') -> each hardlinked copy of mofin_db.py pointed to a DIFFERENT database 3. 'Backup habit' left .bak/legacy files in production dirs, which monitoring then scanned and reported as false alarms 4. Half-done migrations: DB tables created but old JSON writers/readers stayed (price_events), old files stayed 5. Dead modules never got buried: xiaoguo 'dead' but bot ran 8 days as root eating 2.5GB 6. Monitoring checked 'does it exist' not 'is it alive' -> stale file mtime reported as 'pipeline stalled 14 days' (false alarm) 7. No 'system hygiene' as a check category at all Prevention implemented: - dev-spec.md v2.0: 五条红线 -> 十条红线 #6 single source of truth (hardlink only, no independent copies) #7 absolute data paths only (no __file__-relative data resolution) #8 no backups/legacy in production data dirs (archive immediately) #9 dead module burial checklist (6 mandatory steps) #10 monitor liveness (DB table freshness) not existence - File Location Constitution: canonical location per content type - NEW system_hygiene_audit.py: weekly Monday 07:30 cron checking diverged copies / broken hardlinks / zombie processes / orphan data files / dead cron scripts / DB freshness -> hygiene_report.json + XMPP - specs/hygiene.json: module spec per red line #1 - Verified: audit found 5 real issues on first run, all fixed, re-run clean
92 lines
4.4 KiB
Python
92 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""audit_deadcode.py — 废弃模块/死代码审计:小果生态、旧bot、疑似废弃脚本"""
|
|
import os, subprocess, json
|
|
from datetime import datetime
|
|
|
|
def sh(cmd, timeout=10):
|
|
try:
|
|
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
|
|
return r.stdout.strip()
|
|
except Exception as e:
|
|
return f"ERR: {e}"
|
|
|
|
print("=== 1. 小果(xiaoguo)生态残留 ===")
|
|
print("--- 运行中的 xiaoguo 进程 ---")
|
|
print(sh("ps aux | grep -i xiaoguo | grep -v grep"))
|
|
print("\n--- xiaoguo 相关文件 ---")
|
|
print(sh("ls -la /home/hmo/MoFin/deploy/profile-scripts/ 2>/dev/null | grep -i xiaoguo; ls /home/hmo/MoFin/ | grep -i xiaoguo; ls /home/hmo/MoFin/scripts/ 2>/dev/null | grep -i xiaoguo; ls /home/hmo/AgentsMeeting/ 2>/dev/null | grep -i xiaoguo | head -5"))
|
|
print("\n--- xiaoguo 相关数据文件 ---")
|
|
print(sh("ls -la /home/hmo/web-dashboard/data/ | grep -i xiaoguo; ls -la /home/hmo/MoFin/data/ 2>/dev/null | grep -i xiaoguo"))
|
|
print("\n--- xiaoguo 相关 cron ---")
|
|
for jf, prof in [('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json', 'pa'), ('/home/hmo/.hermes/cron/jobs.json', 'default')]:
|
|
try:
|
|
d = json.load(open(jf))
|
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
|
for j in jobs:
|
|
if 'xiaoguo' in str(j.get('script', '')).lower() or '小果' in str(j.get('name', '')):
|
|
print(f" [{prof}] {j.get('name')} script={j.get('script')} enabled={j.get('enabled')} last={str(j.get('last_run_at'))[:10]} status={j.get('last_status')}")
|
|
except Exception as e:
|
|
print(f" {prof}: {e}")
|
|
|
|
print("\n=== 2. 旧 bot 文件 ===")
|
|
for f in ['/home/hmo/xmpp_zhiwei_bot.py.bak', '/home/hmo/run_zhiwei_bot.py', '/home/hmo/xmpp_bot_rest.py',
|
|
'/home/hmo/xmpp_xiaoguo_bot.py', '/home/hmo/xmpp_mohe_bot.py', '/home/hmo/xmpp_zhiwei_bot.py']:
|
|
if os.path.exists(f):
|
|
p = f
|
|
mt = datetime.fromtimestamp(os.path.getmtime(p))
|
|
link = os.path.islink(p)
|
|
tgt = os.readlink(p) if link else ''
|
|
print(f" {f} {'symlink->'+tgt if link else str(os.path.getsize(p)//1024)+'KB'} {mt.strftime('%m-%d')}")
|
|
|
|
print("\n=== 3. AgentsMeeting 目录与 MoFin 的关系(是否重复项目)===")
|
|
print(sh("du -sh /home/hmo/AgentsMeeting /home/hmo/MoFin /home/hmo/web-dashboard /home/hmo/projects/AgentsMeeting /home/hmo/projects/MoFin 2>/dev/null"))
|
|
print("\n--- AgentsMeeting 里和 MoFin 同名的脚本 ---")
|
|
am = '/home/hmo/AgentsMeeting'
|
|
if os.path.isdir(am):
|
|
am_scripts = set()
|
|
for r, _, fs in os.walk(am):
|
|
if 'venv' in r or 'node_modules' in r or '.git' in r:
|
|
continue
|
|
for f in fs:
|
|
if f.endswith('.py'):
|
|
am_scripts.add(f)
|
|
mofin_scripts = set()
|
|
for d in ['/home/hmo/MoFin', '/home/hmo/MoFin/scripts', '/home/hmo/MoFin/deploy/profile-scripts']:
|
|
if os.path.isdir(d):
|
|
mofin_scripts.update(f for f in os.listdir(d) if f.endswith('.py'))
|
|
common = sorted(am_scripts & mofin_scripts)
|
|
print(f" 同名 .py 文件: {len(common)} 个")
|
|
for f in common[:20]:
|
|
print(f" {f}")
|
|
|
|
print("\n=== 4. 超过30天未修改的可疑废弃脚本(deploy/profile-scripts 中)===")
|
|
now = datetime.now()
|
|
dd = '/home/hmo/MoFin/deploy/profile-scripts'
|
|
for f in sorted(os.listdir(dd)):
|
|
if not f.endswith('.py'):
|
|
continue
|
|
p = os.path.join(dd, f)
|
|
mt = datetime.fromtimestamp(os.path.getmtime(p))
|
|
age = (now - mt).total_seconds() / 86400
|
|
if age > 30:
|
|
print(f" {f:45s} {mt.strftime('%m-%d')} ({age:.0f}d)")
|
|
|
|
print("\n=== 5. scripts/ 下的工具脚本(非 cron 调度,疑似一次性)===")
|
|
# 哪些脚本不在 cron jobs 里
|
|
cron_scripts = set()
|
|
for jf in ['/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json', '/home/hmo/.hermes/cron/jobs.json']:
|
|
try:
|
|
d = json.load(open(jf))
|
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
|
for j in jobs:
|
|
if j.get('script'):
|
|
cron_scripts.add(j['script'])
|
|
except Exception:
|
|
pass
|
|
for d in ['/home/hmo/MoFin/deploy/profile-scripts']:
|
|
for f in sorted(os.listdir(d)):
|
|
if f.endswith('.py') and f not in cron_scripts:
|
|
p = os.path.join(d, f)
|
|
mt = datetime.fromtimestamp(os.path.getmtime(p))
|
|
age = (now - mt).total_seconds() / 86400
|
|
print(f" {f:45s} 不在cron中 ({age:.0f}d)") |