import pathlib,json,sys,hashlib,datetime,shutil,subprocess,sqlite3 root=pathlib.Path('/home/ps/huobao-gpu-selection');work=root/'panel-8189' assert (work/'deployed.json').exists(),'8189 has not passed node/history checks' manifest=json.loads((root/'panel-routing-manifest.json').read_text()) stamp=datetime.datetime.now().strftime('%Y%m%d-%H%M%S');backup=root/('before-panel-routing-'+stamp);backup.mkdir(mode=0o700) targets=[('general','canvas-h3-prompt-normalizer-healthy-20261026'),('vdn','huobao-vdn-normalizer')] for short,name in targets: p=root/(name+'.candidate.py');assert hashlib.sha256(p.read_bytes()).hexdigest()==manifest[short]['before'],'Live normalizer changed; review first' candidate=root/(short+'-normalizer-panels.py');assert hashlib.sha256(candidate.read_bytes()).hexdigest()==manifest[short]['after'];compile(candidate.read_text(),str(candidate),'exec') bridges=[('/home/ps/huobao-model-bridge','huobao-model-bridge','state'),('/home/ps/huobao-vdn/bridge','huobao-vdn-bridge','../bridge-state')] for folder,_,_ in bridges:assert hashlib.sha256((pathlib.Path(folder)/'bridge.py').read_bytes()).hexdigest()==manifest['bridge']['before'],'Bridge changed; review first' extra=root/'extra-providers.json';shutil.copy2(extra,backup/'extra-providers.json') state=json.loads((root/'state.json').read_text());admission=pathlib.Path(state['admission']);shutil.copy2(admission,backup/'admission.json') # Stage code that accepts both provider IDs before exposing the new provider entry. for short,name in targets: live=root/(name+'.candidate.py');shutil.copy2(live,backup/(name+'.py')) live.write_bytes((root/(short+'-normalizer-panels.py')).read_bytes()) subprocess.run(['docker','restart','--timeout','60',name],check=True,stdout=subprocess.DEVNULL) normalizer=json.loads(subprocess.check_output(['docker','inspect',targets[0][1]],text=True))[0] tokens=pathlib.Path(next(m['Source'] for m in normalizer['Mounts'] if m['Destination']=='/run/h3-workers')) token=tokens/'gpu1_8189.token';assert not token.exists(),'New token already exists; inspect first' token.write_bytes((work/'control/canary.token').read_bytes());token.chmod(0o600) subprocess.run(['setfacl','-m','u:65534:r--',str(token)],check=True) assert token.stat().st_mode & 0o007==0 providers=json.loads(extra.read_text());providers.append({'id':'gpu1_8189','name':'ComfyUI GPU1 :8189','base_url':'http://192.168.110.111:8189','control_token_file':'/run/h3-workers/gpu1_8189.token'}) extra.write_text(json.dumps(providers));extra.chmod(0o644) policy=json.loads(admission.read_text());policy['admit']=[i for i in policy['admit'] if i!='gpu1']+['gpu1_8189'] admission.write_text(json.dumps(policy)) # Preserve all old IDs and job tables; only new Huobao submissions use the panel pool. candidate=root/'bridge-panels.py';assert hashlib.sha256(candidate.read_bytes()).hexdigest()==manifest['bridge']['after'] for folder,name,statefolder in bridges: p=pathlib.Path(folder);dest=backup/name;dest.mkdir(mode=0o700) shutil.copy2(p/'bridge.py',dest/'bridge.py');shutil.copy2(p/'config.json',dest/'config.json') with sqlite3.connect(p/statefolder/'jobs.sqlite3') as src,sqlite3.connect(dest/'jobs.sqlite3') as target:src.backup(target) (p/'bridge.py').write_bytes(candidate.read_bytes()) subprocess.run(['docker','restart','--timeout','60',name],check=True,stdout=subprocess.DEVNULL) (root/'panel-routing-deployed.json').write_text(json.dumps({'backup':str(backup),'ports':{'gpu1':8189,'gpu2':8188,'gpu3':8191},'legacy_worker_retained':True})) print(json.dumps({'backup':str(backup),'new_submission_ports':[8188,8189,8191],'legacy_jobs_preserved':True}))