53 lines
3.9 KiB
Python
53 lines
3.9 KiB
Python
"""Prevent verified deleted historical tasks from resurfacing via signed restore snapshots."""
|
|
import contextlib,hashlib,importlib.util,json,pathlib,shutil,sqlite3,subprocess,time
|
|
ROOT=pathlib.Path('/home/ps/huobao-video-cleanup');OUT=ROOT/'historical-20261112'
|
|
cfg=json.loads((ROOT/'config.json').read_text());approved=json.loads((ROOT/'historical-approved.json').read_text())
|
|
assert (OUT/'enqueue-done.json').exists()
|
|
candidate=ROOT/'comfy_video_cleanup.candidate.py';compile(candidate.read_text(),str(candidate),'exec')
|
|
assert 'def reconcile_cleaned_history(' in candidate.read_text()
|
|
bridges={b['name']:b for b in cfg['bridges']};byworker={}
|
|
for a in approved:byworker.setdefault(a['worker'],[]).append(a)
|
|
workers=json.loads(subprocess.check_output(['docker','inspect',*byworker],text=True))
|
|
added=[]
|
|
for b in bridges.values():
|
|
backup=OUT/(b['name']+'-before-history-guard.sqlite3')
|
|
if not backup.exists():
|
|
with contextlib.closing(sqlite3.connect('file:'+b['database']+'?mode=ro',uri=True)) as source,contextlib.closing(sqlite3.connect(backup)) as target:source.backup(target)
|
|
for i in workers:
|
|
env=dict(x.split('=',1) for x in i['Config'].get('Env',[]) if '=' in x);dest=env.get('H3_HISTORY_SNAPSHOT')
|
|
if not dest:continue
|
|
mount=next((m for m in i['Mounts'] if m['Destination']==dest),None)
|
|
if not mount:continue
|
|
snapshot=json.loads(pathlib.Path(mount['Source']).read_text())
|
|
for a in byworker[i['Name'].lstrip('/')]:
|
|
history=snapshot.get('history',{}).get(a['manifest']['prompt'])
|
|
if not history:continue
|
|
locations=[]
|
|
for node in (history.get('outputs') or {}).values():
|
|
for field in ('images','gifs','audio'):
|
|
for loc in node.get(field) or []:
|
|
if isinstance(loc,dict):locations.append({k:loc.get(k,'output' if k=='type' else '') for k in ('filename','subfolder','type')})
|
|
assert a['manifest']['locator'] in locations,'Snapshot video locator does not match verified deleted source'
|
|
h=hashlib.sha256(json.dumps(history,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest()
|
|
with contextlib.closing(sqlite3.connect(bridges[a['bridge']]['database'],timeout=15)) as db,db:
|
|
db.row_factory=sqlite3.Row;row=db.execute('select status,manifest from video_cleanup_acks where id=?',(a['id'],)).fetchone()
|
|
assert row and row['status']=='cleaned','Wait for exact video cleanup'
|
|
manifest=json.loads(row['manifest']);assert manifest['fileReceipt']==a['fileReceipt']
|
|
manifest['restoredHistoryHashes']=sorted(set(manifest.get('restoredHistoryHashes',[])+[h]))
|
|
db.execute('update video_cleanup_acks set manifest=? where id=?',(json.dumps(manifest),a['id']))
|
|
added.append(a['task'])
|
|
# All approved deletions must finish before restarting only the private cleanup helper.
|
|
for a in approved:
|
|
with contextlib.closing(sqlite3.connect('file:'+bridges[a['bridge']]['database']+'?mode=ro',uri=True)) as db:
|
|
assert db.execute('select status from video_cleanup_acks where id=?',(a['id'],)).fetchone()[0]=='cleaned'
|
|
old=OUT/'comfy_video_cleanup-before-history-guard.py'
|
|
if not old.exists():shutil.copy2(ROOT/'comfy_video_cleanup.py',old)
|
|
shutil.copy2(candidate,ROOT/'comfy_video_cleanup.py')
|
|
subprocess.run(['systemctl','--user','restart','huobao-video-cleanup.service'],check=True,capture_output=True)
|
|
assert subprocess.check_output(['systemctl','--user','is-active','huobao-video-cleanup.service'],text=True).strip()=='active'
|
|
before=json.loads((OUT/'services-before.json').read_text())
|
|
after={i['Name']:{'id':i['Id'],'started':i['State']['StartedAt']} for i in json.loads(subprocess.check_output(['docker','inspect',*[x.lstrip('/') for x in before]],text=True))}
|
|
assert before==after,'Model service identity changed'
|
|
report={'historyGuardInstalled':True,'snapshotTasksProtected':added,'pollSeconds':60,'modelServicesUnchanged':True,'snapshotFilesUnchanged':True,'codeSha256':hashlib.sha256((ROOT/'comfy_video_cleanup.py').read_bytes()).hexdigest()}
|
|
(OUT/'history-guard-report.json').write_text(json.dumps(report,indent=2));print(json.dumps(report))
|