#!/usr/bin/env python3 """Bind the WebUI client and server to one immutable release identity.""" from __future__ import annotations import os from pathlib import Path import re ROOT = Path(os.environ.get("HERMES_WEBUI_PATCH_ROOT", "/opt/hermes-webui")) RELEASE_ID = os.environ.get("HERMES_WEBUI_RELEASE_ID", "").strip() if not re.fullmatch(r"[a-z0-9][a-z0-9.-]{0,127}", RELEASE_ID): raise SystemExit("HERMES_WEBUI_RELEASE_ID must be a safe immutable release token") for relative in ("static/index.html", "static/sw.js"): path = ROOT / relative source = path.read_text(encoding="utf-8") if "__WEBUI_VERSION__" not in source: raise SystemExit(f"Hermes release patch context changed in {path}") path.write_text( source.replace("__WEBUI_VERSION__", f"__WEBUI_VERSION__-{RELEASE_ID}"), encoding="utf-8", ) routes = ROOT / "api/routes.py" routes_source = routes.read_text(encoding="utf-8") routes_marker = ' settings["webui_version"] = WEBUI_VERSION\n' if routes_source.count(routes_marker) != 1: raise SystemExit(f"Hermes release settings context changed in {routes}") routes.write_text( routes_source.replace( routes_marker, routes_marker + " settings[\"webui_bundle_version\"] = " + f'f"{{WEBUI_VERSION}}-{RELEASE_ID}"\n', ), encoding="utf-8", ) panels = ROOT / "static/panels.js" panels_source = panels.read_text(encoding="utf-8") server_marker = ( " const server=_normalizeWebUIVersion(settings.webui_version);\n" ) server_replacement = ( " const server=_normalizeWebUIVersion(" "settings.webui_bundle_version||settings.webui_version);\n" ) if panels_source.count(server_marker) != 1: raise SystemExit(f"Hermes release skew context changed in {panels}") matching_marker = " if(client===server) return;\n" matching_replacement = ( " if(client===server){\n" " const banner=document.getElementById('staleClientBanner');\n" " if(banner) banner.style.display='none';\n" " return;\n" " }\n" ) if panels_source.count(matching_marker) != 1: raise SystemExit(f"Hermes release match context changed in {panels}") poll_marker = " if(_isBannerVisible()) return;\n" if panels_source.count(poll_marker) != 1: raise SystemExit(f"Hermes release poll context changed in {panels}") visible_stop_marker = ( " if(_isBannerVisible()){ clearInterval(_pollTimer); " "_pollTimer=null; return; }\n" ) if panels_source.count(visible_stop_marker) != 1: raise SystemExit(f"Hermes release monitor context changed in {panels}") panels.write_text( panels_source.replace(server_marker, server_replacement) .replace(matching_marker, matching_replacement) .replace(poll_marker, "") .replace(visible_stop_marker, ""), encoding="utf-8", )