#!/usr/bin/env python3 """Publish the reviewed local agent source into the controller static release.""" import ast import hashlib import json from pathlib import Path import re def manifest_bytes(agent, updater, installer): """Return the deterministic bytes signed by the offline release signer.""" # Inspect source without importing or executing release code. versions = [node.value.value for node in ast.parse(agent).body if isinstance(node, ast.Assign) and any(isinstance(name, ast.Name) and name.id == "VERSION" for name in node.targets) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)] if len(versions) != 1 or not re.fullmatch(r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)", versions[0]): raise ValueError("Agent source must declare exactly one literal semantic VERSION") compile(agent, "ec_agent.py", "exec") compile(updater, "ec_agent_update.py", "exec") manifest = {"version": versions[0], "protocolVersion": 1, "platform": "debian-amd64", "sha256": hashlib.sha256(agent).hexdigest(), "updaterProtocolVersion": 2, "updaterSha256": hashlib.sha256(updater).hexdigest(), "installerSha256": hashlib.sha256(installer).hexdigest()} return (json.dumps(manifest, indent=2) + "\n").encode("utf-8") def package(source, target): """Read version metadata without importing or executing release code.""" source, target = Path(source), Path(target) agent = (source / "ec_agent.py").read_bytes() updater = (source / "ec_agent_update.py").read_bytes() installer = (source / "install.sh").read_bytes() manifest = manifest_bytes(agent, updater, installer) target.mkdir(parents=True, exist_ok=True) try: previous_manifest = (target / "manifest.json").read_bytes() except FileNotFoundError: previous_manifest = None if previous_manifest != manifest: # Builds never sign. Keep an offline signature only across identical # rebuilds; changed releases remain unsigned until reviewed and signed. (target / "manifest.json.sig").unlink(missing_ok=True) (target / "ec-agent.py").write_bytes(agent) (target / "ec-agent-update.py").write_bytes(updater) (target / "install.sh").write_bytes(installer) temporary_manifest = target / ".manifest.json.tmp" temporary_manifest.write_bytes(manifest) temporary_manifest.replace(target / "manifest.json") if __name__ == "__main__": source = Path(__file__).resolve().parent package(source, source.parent / "static" / "agent")