Files
dj/integration-work/deploy_video_cleanup.py
T
2026-11-13 11:46:25 +08:00

69 lines
5.2 KiB
Python

"""Install receipt-driven cleanup; restart only private HTTP bridges, never workers."""
import datetime,hashlib,json,pathlib,shutil,sqlite3,subprocess,time,urllib.request,urllib.error
root=pathlib.Path('/home/ps/huobao-video-cleanup')
expected='646600a3c34354689b8d5aec0927b7481583da5478619e4e172f42583e5f4ffc'
bridges=[{'name':'huobao-model-bridge','folder':'/home/ps/huobao-model-bridge','database':'/home/ps/huobao-model-bridge/state/jobs.sqlite3','normalizer':'canvas-h3-prompt-normalizer-healthy-20261026','port':18792},
{'name':'huobao-vdn-bridge','folder':'/home/ps/huobao-vdn/bridge','database':'/home/ps/huobao-vdn/bridge-state/jobs.sqlite3','normalizer':'huobao-vdn-normalizer','port':18794}]
workers={'default':'comfyui','gpu1_8189':'comfyui-manju','gpu3':'h3-caption-gpu3-canary-8a5f','gpu1':'huobao-comfy-gpu1'}
names=list(workers.values())+[b['normalizer'] for b in bridges]
def identities():
return {i['Name']:{'id':i['Id'],'started':i['State']['StartedAt']} for i in json.loads(subprocess.check_output(['docker','inspect',*names],text=True))}
before=identities()
for filename in ['comfy_video_cleanup.candidate.py','video_cleanup_api.candidate.py','bridge-cleanup.candidate.py']:
compile((root/filename).read_text(),filename,'exec')
for entry in bridges:
path=pathlib.Path(entry['folder'])/'bridge.py'
assert hashlib.sha256(path.read_bytes()).hexdigest()==expected,'Bridge changed since review'
assert not (path.parent/'video_cleanup_api.py').exists(),'Unexpected pre-existing cleanup module'
unit=pathlib.Path.home()/'.config/systemd/user/huobao-video-cleanup.service'
assert not unit.exists(),'Unexpected existing service; review first'
backup=root/('backup-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S'));backup.mkdir(mode=0o700)
for entry in bridges:
folder=pathlib.Path(entry['folder']);shutil.copy2(folder/'bridge.py',backup/(entry['name']+'.py'))
with sqlite3.connect('file:'+entry['database']+'?mode=ro',uri=True) as source,sqlite3.connect(backup/(entry['name']+'.sqlite3')) as target:source.backup(target)
shutil.copy2(root/'comfy_video_cleanup.candidate.py',root/'comfy_video_cleanup.py')
config={'bridges':[{k:b[k] for k in ['name','database','normalizer']}|{'workers':workers} for b in bridges]}
(root/'config.json').write_text(json.dumps(config,indent=2));(root/'config.json').chmod(0o600)
unit.parent.mkdir(parents=True,exist_ok=True)
unit.write_text('[Unit]\nDescription=Huobao verified video cleanup\n\n[Service]\nType=simple\nWorkingDirectory=/home/ps/huobao-video-cleanup\nExecStart=/usr/bin/python3 /home/ps/huobao-video-cleanup/comfy_video_cleanup.py /home/ps/huobao-video-cleanup/config.json\nRestart=on-failure\nRestartSec=10\nTimeoutStopSec=250\nNoNewPrivileges=true\nUMask=0077\n\n[Install]\nWantedBy=default.target\n')
def request(entry,path,body=None,auth=True):
cfg=json.loads((pathlib.Path(entry['folder'])/'config.json').read_text())
headers={'Content-Type':'application/json'}
if auth:headers['Authorization']='Bearer '+cfg['token']
req=urllib.request.Request(f"http://192.168.110.111:{entry['port']}"+path,data=None if body is None else json.dumps(body).encode(),headers=headers)
try:
with urllib.request.urlopen(req,timeout=15) as response:return response.status
except urllib.error.HTTPError as error:return error.code
changed=[]
try:
for entry in bridges:
folder=pathlib.Path(entry['folder']);shutil.copy2(root/'video_cleanup_api.candidate.py',folder/'video_cleanup_api.py')
shutil.copy2(root/'bridge-cleanup.candidate.py',folder/'bridge.py');changed.append(entry)
subprocess.run(['docker','restart','--timeout','60',entry['name']],check=True,capture_output=True)
for attempt in range(15):
try:
if request(entry,'/v1/models')==200:break
except OSError:pass
time.sleep(1)
else:raise RuntimeError('Bridge health check failed')
receipt={'version':1,'index':0,'sha256':'a'*64,'bytes':1}
endpoint='/v2/video_generation/unknown-cleanup-selftest/cleanup'
assert request(entry,endpoint,receipt)==404
assert request(entry,endpoint,receipt,auth=False)==401
with sqlite3.connect(entry['database']) as db:assert db.execute('select count(*) from video_cleanup_acks').fetchone()[0]==0
print(entry['name'],'receipt endpoint ready',flush=True)
subprocess.run(['systemctl','--user','daemon-reload'],check=True,capture_output=True)
subprocess.run(['systemctl','--user','enable','--now','huobao-video-cleanup.service'],check=True,capture_output=True)
time.sleep(1)
assert subprocess.check_output(['systemctl','--user','is-active','huobao-video-cleanup.service'],text=True).strip()=='active'
assert identities()==before,'Unexpected model/normalizer restart'
report={'installed':True,'backup':str(backup),'service':'huobao-video-cleanup.service','model_services_not_restarted':True,'existing_jobs_not_cancelled':True,'pending_cleanup_requests':0}
(root/'deployment.json').write_text(json.dumps(report,indent=2));print(json.dumps(report))
except Exception:
subprocess.run(['systemctl','--user','disable','--now','huobao-video-cleanup.service'],capture_output=True)
for entry in changed:
folder=pathlib.Path(entry['folder']);shutil.copy2(backup/(entry['name']+'.py'),folder/'bridge.py')
(folder/'video_cleanup_api.py').unlink(missing_ok=True)
subprocess.run(['docker','restart','--timeout','60',entry['name']],capture_output=True)
raise