import pathlib,subprocess,datetime,shutil,time,urllib.request,hashlib,json base=pathlib.Path('/www/dk_project/dk_app/trilium') release=pathlib.Path('/tmp/trilium-korean-ui-phase2') files={'translation.json':'i18n_ko/translation.json','layout_commons.js':'patched/layout_commons.js','ckeditor-ko.js':'patched/ckeditor-ko.js','ckeditor5.js':'patched/ckeditor5.js','MindElixir.js':'patched/MindElixir.js','emoji-ko.json':'patched/emoji-ko.json'} compose=base/'docker-compose.yml' before=compose.read_text() anchor=' - ./patched/layout_commons.js:/usr/src/app/public/src/layout_commons.js:ro' assert anchor in before main=base/'patched/main.cjs' oldmain=main.read_text() oldprefix='jg=`assets/v${CR.version}-ko-20260905`' newprefix='jg=`assets/v${CR.version}-ko-20260905-r2`' assert oldprefix in oldmain assert 'e.use(`/${jg}/src`,TQ(x2.default.join(n,"src")))' in oldmain for src in files: assert (release/src).is_file() and (release/src).stat().st_size>100 assert not (base/'data/korean-notes-pending.json').exists(), 'Pending data migration must finish first' stamp=datetime.datetime.now().strftime('%Y%m%d-%H%M%S') backup=base/'backups'/('before-korean-ui-phase2-'+stamp) backup.mkdir(parents=True,mode=0o700) targets=list(files.values())+['patched/main.cjs','docker-compose.yml'] existing=[] for rel in targets: target=base/rel if target.exists(): dest=backup/rel;dest.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(target,dest);existing.append(rel) (backup/'manifest.json').write_text(json.dumps({'existing':existing,'targets':targets})) after=before for name in ['ckeditor5.js','MindElixir.js','emoji-ko.json']: mount=' - ./patched/'+name+':/usr/src/app/public/src/'+name+':ro' if mount not in after:after=after.replace(anchor,anchor+'\n'+mount) try: for src,rel in files.items():(base/rel).write_bytes((release/src).read_bytes()) main.write_text(oldmain.replace(oldprefix,newprefix)) compose.write_text(after) subprocess.run(['docker','compose','-f',str(compose),'config','--quiet'],check=True) subprocess.run(['docker','compose','-f',str(compose),'up','-d','--no-deps','trilium'],check=True) for attempt in range(25): try: with urllib.request.urlopen('http://127.0.0.1:18082/api/health-check',timeout=3) as response:assert response.status==200 for src in ['layout_commons.js','ckeditor-ko.js','ckeditor5.js','MindElixir.js','emoji-ko.json']: url='http://127.0.0.1:18082/assets/v0.95.0-ko-20260905-r2/src/'+src with urllib.request.urlopen(url,timeout=5) as response: assert hashlib.sha256(response.read()).digest()==hashlib.sha256((release/src).read_bytes()).digest(),src print(json.dumps({'status':'verified','backup':str(backup),'servedAssets':5}));break except Exception: if attempt==24:raise time.sleep(1) except Exception: for rel in existing:(base/rel).write_bytes((backup/rel).read_bytes()) subprocess.run(['docker','compose','-f',str(compose),'up','-d','--no-deps','trilium'],check=True) print('Restored previous files and compose configuration from '+str(backup)) raise