mirror of
https://github.com/This-weiyuqing/mi-fitness-health.git
synced 2026-10-18 03:03:49 +08:00
95 lines
4.8 KiB
Python
95 lines
4.8 KiB
Python
"""只清理明确列出的旧归档,不操作容器、镜像或运行数据。"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import stat
|
|
import subprocess
|
|
from datetime import datetime, timezone
|
|
|
|
ROOT = Path('/vol2/1000/Soft/mi-fitness-health')
|
|
KEEP = 'directory-migration-retry-1792227666562967051'
|
|
OBSOLETE = ['initial-install', 'password-change', 'rollback-configs', 'ui4', 'ui5', 'ui6', 'ui7',
|
|
'directory-migration', 'directory-migration-retry-1792227359874267909']
|
|
EXPECTED = {'compose.yml': 'b5c965c653948e97e02e22774bd15253dbde74d5e25616fa0115f047a36556dd',
|
|
'compose.override.yml': 'd77485b3d3177def55c200478ba9e4645689f8476f46c13a47487be22edcd1c1'}
|
|
|
|
|
|
def candidates(root):
|
|
root = Path(root)
|
|
archive = root/'archive'
|
|
if root.is_symlink() or root.resolve() != root or archive.is_symlink() or archive.resolve().parent != root:
|
|
raise RuntimeError('项目或归档路径异常')
|
|
paths = []
|
|
for name in OBSOLETE:
|
|
path = archive/name
|
|
if path.is_symlink() or path.resolve().parent != archive or not path.is_dir():
|
|
raise RuntimeError('历史目录缺失或路径异常,未清理任何文件')
|
|
for child in path.rglob('*'):
|
|
if not (stat.S_ISREG(child.lstat().st_mode) or stat.S_ISDIR(child.lstat().st_mode)):
|
|
raise RuntimeError('历史目录包含链接或特殊文件,未清理任何文件')
|
|
paths.append(path)
|
|
return paths
|
|
|
|
|
|
def main():
|
|
if os.geteuid() != 0:
|
|
raise RuntimeError('需要通过 sudo 执行')
|
|
paths = candidates(ROOT)
|
|
keep = ROOT/'archive'/KEEP
|
|
if keep.is_symlink() or json.loads((keep/'result.json').read_text(encoding='utf-8'))['status'] != 'complete':
|
|
raise RuntimeError('成功迁移记录异常,停止')
|
|
for name, expected in EXPECTED.items():
|
|
path = ROOT/name
|
|
if path.is_symlink() or hashlib.sha256(path.read_bytes()).hexdigest() != expected:
|
|
raise RuntimeError('当前配置已变化,停止')
|
|
ids = subprocess.check_output(['docker', 'ps', '-aq']).decode().split()
|
|
items = json.loads(subprocess.check_output(['docker','inspect',*ids],stderr=subprocess.PIPE)) if ids else []
|
|
for item in items:
|
|
for mount in item.get('Mounts', []):
|
|
if mount.get('Type') != 'bind':
|
|
continue
|
|
source = Path(mount['Source']).resolve()
|
|
if any(source == p or source.is_relative_to(p) or p.is_relative_to(source) for p in paths):
|
|
raise RuntimeError('容器引用历史归档,停止')
|
|
services = {'/mi-fitness-health-'+x+'-1' for x in ['web','backend','worker']}
|
|
current = [i for i in items if i['Name'] in services]
|
|
if len(current)!=3 or not all(i['State']['Running'] for i in current):
|
|
raise RuntimeError('健康容器状态异常,停止')
|
|
snapshots = [(i['Id'], i['State']['StartedAt']) for i in current]
|
|
record = {'at': datetime.now(timezone.utc).isoformat(), 'directories':[], 'bytes_removed':0, 'files_removed':0}
|
|
for path in paths:
|
|
files = [p for p in path.rglob('*') if p.is_file()]
|
|
size = sum(p.stat().st_size for p in files)
|
|
record['directories'].append({'name':path.name,'files':len(files),'bytes':size})
|
|
record['bytes_removed'] += size
|
|
record['files_removed'] += len(files)
|
|
# 保留当前可用配置,而非仅留下引用旧路径的迁移前配置。
|
|
for name in EXPECTED:
|
|
shutil.copy2(ROOT/name, keep/('current-'+name))
|
|
(keep/'cleanup-plan.json').write_text(json.dumps(record,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
|
for path in paths:
|
|
if path.is_symlink() or path.resolve().parent != ROOT/'archive':
|
|
raise RuntimeError('删除前路径核对失败,停止')
|
|
shutil.rmtree(path)
|
|
print('已清理:'+path.name,flush=True)
|
|
after = json.loads(subprocess.check_output(['docker','inspect',*[i['Id'] for i in current]],stderr=subprocess.PIPE))
|
|
if snapshots != [(i['Id'],i['State']['StartedAt']) for i in after] or not all(i['State']['Running'] for i in after):
|
|
raise RuntimeError('容器状态发生变化,需核对')
|
|
for name, expected in EXPECTED.items():
|
|
if hashlib.sha256((ROOT/name).read_bytes()).hexdigest()!=expected:
|
|
raise RuntimeError('当前配置发生变化,需核对')
|
|
record['status']='complete'
|
|
record['containers_unchanged']=True
|
|
(keep/'cleanup-result.json').write_text(json.dumps(record,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
|
print('历史文件清理完成,释放约 %.1f MiB;三个容器未重启。' % (record['bytes_removed']/1024**2),flush=True)
|
|
|
|
|
|
if __name__=='__main__':
|
|
try:
|
|
main()
|
|
except Exception as error:
|
|
print(str(error) if type(error) is RuntimeError else type(error).__name__,flush=True)
|
|
raise SystemExit(1)
|