#!/usr/bin/env python3 """audit_duplication.py — 扫描 MoFin 相关目录的文件重复情况""" import os, hashlib, json from collections import defaultdict LOCATIONS = [ '/home/hmo/MoFin', '/home/hmo/MoFin/scripts', '/home/hmo/MoFin/deploy/profile-scripts', '/home/hmo/MoFin/deploy/bot', '/home/hmo/.hermes/profiles/position-analyst/scripts', '/home/hmo/.hermes/scripts', '/home/hmo/web-dashboard', ] def md5(p): try: with open(p, 'rb') as f: return hashlib.md5(f.read()).hexdigest()[:10] except Exception: return 'ERR' def ino(p): try: return os.stat(p).st_ino except Exception: return 0 # 收集所有 .py 文件 files = defaultdict(list) # name -> [(loc, path, md5, inode, is_link)] for loc in LOCATIONS: if not os.path.isdir(loc): continue for f in os.listdir(loc): if not f.endswith('.py'): continue p = os.path.join(loc, f) if not os.path.isfile(p): continue files[f].append({ 'loc': loc, 'path': p, 'md5': md5(p), 'inode': ino(p), 'is_link': os.path.islink(p), 'size': os.path.getsize(p), 'mtime': int(os.path.getmtime(p)), }) print("=== 重复文件(同名出现在2+位置)===") dups = {k: v for k, v in files.items() if len(v) > 1} identical = 0 hardlinked = 0 diverged = 0 for name in sorted(dups): entries = dups[name] md5s = set(e['md5'] for e in entries) inodes = set(e['inode'] for e in entries) if len(inodes) == 1: status = 'HARDLINK(同一文件)' hardlinked += 1 elif len(md5s) == 1: status = 'COPY(内容相同,多份独立)' identical += 1 else: status = 'DIVERGED(内容不同!)' diverged += 1 print(f"{status} {name}") for e in entries: print(f" {e['path']} md5={e['md5']} ino={e['inode']} size={e['size']}") print(f"\n汇总: {len(dups)} 个重复文件名 | hardlink={hardlinked} 内容相同副本={identical} 内容分叉={diverged}") print("\n=== deploy/profile-scripts 中有但 position-analyst/scripts 中缺失的 ===") pa_dir = '/home/hmo/.hermes/profiles/position-analyst/scripts' deploy_dir = '/home/hmo/MoFin/deploy/profile-scripts' pa_files = set(os.listdir(pa_dir)) if os.path.isdir(pa_dir) else set() for f in sorted(os.listdir(deploy_dir)): if f.endswith('.py') and f not in pa_files: print(f" {f}") print("\n=== position-analyst/scripts 中有但 deploy 中没有的(可能孤儿)===") deploy_files = set(os.listdir(deploy_dir)) for f in sorted(pa_files): if f.endswith('.py') and f not in deploy_files: p = os.path.join(pa_dir, f) print(f" {f} size={os.path.getsize(p)}")