#!/usr/bin/env python3 """Verify and materialize an immutable NexusCRM deployment bundle.""" from __future__ import annotations import argparse import hashlib import json import os import re import stat import subprocess import tarfile import tempfile from pathlib import Path, PurePosixPath from typing import Any, BinaryIO BUNDLE_CONTRACT_VERSION = "deployment-bundle/v1" BUNDLE_RECEIPT_VERSION = "deployment-bundle-receipt/v1" BUNDLE_MANIFEST_NAME = "deployment-bundle.json" BUNDLE_ARCHIVE_NAME = "deployment-bundle.tar" BUNDLE_RECEIPT_NAME = "deployment-bundle-receipt.json" MATERIALIZED_RECEIPT_NAME = ".deployment-bundle.json" DOWNSTREAM_GIT_BUNDLE_NAME = "downstream.git.bundle" OFFICIAL_APP_PACKS_GIT_BUNDLE_NAME = "official-app-packs.git.bundle" SHA256_RE = re.compile(r"^[0-9a-f]{64}$") COMMIT_RE = re.compile(r"^[0-9a-f]{40}$") class BundleError(RuntimeError): pass def canonical_json_bytes(value: object) -> bytes: return ( json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n" ).encode("utf-8") def sha256_bytes(value: bytes) -> str: return hashlib.sha256(value).hexdigest() def sha256_file(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def tree_sha256(root: Path) -> str: """Match the release final-pack `sha256sum | sha256sum` tree contract.""" root = root.expanduser().resolve() if not root.is_dir() or root.is_symlink(): raise BundleError(f"tree root is missing or invalid: {root}") lines: list[bytes] = [] for path in sorted(root.rglob("*"), key=lambda item: item.relative_to(root).as_posix()): if path.is_dir(): continue if path.is_symlink() or not path.is_file(): raise BundleError(f"tree contains unsupported path: {path}") relative = path.relative_to(root).as_posix() if path.name == ".build-lineage.json": continue lines.append(f"{sha256_file(path)} ./{relative}\n".encode("utf-8")) return sha256_bytes(b"".join(lines)) def require_sha256(value: object, label: str) -> str: normalized = str(value or "").strip() if not SHA256_RE.fullmatch(normalized): raise BundleError(f"{label} must be a lowercase SHA-256") return normalized def require_commit(value: object, label: str) -> str: normalized = str(value or "").strip() if not COMMIT_RE.fullmatch(normalized): raise BundleError(f"{label} must be an exact lowercase Git commit") return normalized def member_payload(archive: tarfile.TarFile, member: tarfile.TarInfo) -> bytes: handle: BinaryIO | None = archive.extractfile(member) if handle is None: raise BundleError(f"archive member has no payload: {member.name}") return handle.read() def require_expected_identity( manifest: dict[str, Any], expected: dict[str, object] ) -> None: source = manifest.get("source") if not isinstance(source, dict): raise BundleError("bundle manifest source is missing") for key, validator in ( ("downstream_ref", require_commit), ("upstream_ref", require_commit), ("official_app_packs_ref", require_commit), ("final_pack_hash", require_sha256), ): expected_value = validator(expected.get(key), f"expected {key}") actual_value = validator(source.get(key), f"bundle {key}") if actual_value != expected_value: raise BundleError(f"bundle {key} does not match expected identity") expected_connect_mcp_url = str(expected.get("connect_mcp_url", "")).strip() actual_connect_mcp_url = str(source.get("connect_mcp_url", "")).strip() if ( not expected_connect_mcp_url or actual_connect_mcp_url != expected_connect_mcp_url ): raise BundleError("bundle connect_mcp_url does not match expected identity") def validate_manifest(manifest: object) -> tuple[dict[str, Any], dict[str, dict[str, object]]]: if not isinstance(manifest, dict): raise BundleError("bundle manifest must be an object") if manifest.get("contract_version") != BUNDLE_CONTRACT_VERSION: raise BundleError("unsupported deployment bundle contract") files = manifest.get("files") if not isinstance(files, list) or not files: raise BundleError("bundle manifest file list is missing") indexed: dict[str, dict[str, object]] = {} for raw in files: if not isinstance(raw, dict): raise BundleError("bundle manifest contains an invalid file entry") name = str(raw.get("path", "")) normalized = PurePosixPath(name) if ( normalized.is_absolute() or ".." in normalized.parts or normalized.as_posix() != name or name in indexed ): raise BundleError(f"bundle manifest contains an unsafe file path: {name}") require_sha256(raw.get("sha256"), f"bundle file {name} sha256") if not isinstance(raw.get("size"), int) or int(raw["size"]) < 0: raise BundleError(f"bundle file {name} has invalid size") if raw.get("mode") not in {0o644, 0o755}: raise BundleError(f"bundle file {name} has invalid mode") indexed[name] = raw return manifest, indexed def materialized_layout(root: Path, manifest: dict[str, Any]) -> dict[str, object]: layout = manifest.get("layout") if not isinstance(layout, dict): raise BundleError("bundle manifest layout is missing") result: dict[str, object] = {"bundle_root": str(root), "manifest": manifest} for key in ( "backend_binary", "setup_binary", "frontend_dist", "downstream_root", "downstream_git_bundle", "upstream_root", "official_app_packs_root", "official_app_packs_git_bundle", "connect_cli", "connect_plugin", "final_pack_dir", ): relative = PurePosixPath(str(layout.get(key, ""))) if relative.is_absolute() or ".." in relative.parts or not relative.parts: raise BundleError(f"bundle layout path is invalid: {key}") result[key] = str(root / Path(*relative.parts)) raw_extras = layout.get("extras", {}) if not isinstance(raw_extras, dict): raise BundleError("bundle extra layout is invalid") extras: dict[str, str] = {} for name, raw_relative in raw_extras.items(): if not re.fullmatch(r"[a-z][a-z0-9_]*", str(name)): raise BundleError(f"bundle extra layout name is invalid: {name}") relative = PurePosixPath(str(raw_relative)) if relative.is_absolute() or ".." in relative.parts or not relative.parts: raise BundleError(f"bundle extra layout path is invalid: {name}") extras[str(name)] = str(root / Path(*relative.parts)) result["extras"] = extras source = manifest["source"] result["connect_version"] = str(source.get("connect_version", "")) result["backend_go_build_tags"] = str(source.get("backend_go_build_tags", "")) return result def git_command(*args: str, cwd: Path | None = None) -> str: command = ["git"] if cwd is not None: command.extend(["-C", str(cwd)]) command.extend(args) result = subprocess.run( command, check=False, capture_output=True, text=True, ) if result.returncode != 0: detail = result.stderr.strip() or result.stdout.strip() or "unknown Git error" raise BundleError(f"cannot materialize exact Git source: {detail}") return result.stdout def require_exact_git_checkout( checkout_root: Path, expected_commit: str, label: str, ) -> Path: expected_commit = require_commit( expected_commit, f"{label} checkout commit", ) checkout_root = checkout_root.expanduser() if checkout_root.is_symlink() or not checkout_root.is_dir(): raise BundleError(f"{label} checkout is missing or invalid") checkout_root = checkout_root.resolve() if git_command("rev-parse", "--show-toplevel", cwd=checkout_root).strip() != str( checkout_root ): raise BundleError(f"{label} checkout root is not exact") if git_command("rev-parse", "HEAD", cwd=checkout_root).strip() != expected_commit: raise BundleError(f"{label} checkout HEAD does not match the expected identity") git_command("cat-file", "-e", f"{expected_commit}^{{commit}}", cwd=checkout_root) if git_command( "status", "--porcelain", "--untracked-files=all", cwd=checkout_root, ).strip(): raise BundleError(f"{label} checkout is not pristine") return checkout_root def materialize_exact_git_checkout( bundle_root: Path, bundle_path: Path, target_root: Path, *, expected_commit: str, label: str, ) -> Path: expected_commit = require_commit(expected_commit, f"{label} commit") bundle_root = bundle_root.expanduser() bundle_path = bundle_path.expanduser() if ( bundle_root.is_symlink() or not bundle_root.is_dir() or bundle_path.is_symlink() or not bundle_path.is_file() ): raise BundleError(f"materialized {label} Git bundle is invalid") bundle_root = bundle_root.resolve() bundle_path = bundle_path.resolve() try: bundle_path.relative_to(bundle_root) except ValueError as exc: raise BundleError( f"materialized {label} Git bundle is outside the deployment bundle" ) from exc listed_heads = git_command("bundle", "list-heads", str(bundle_path)).splitlines() if listed_heads != [f"{expected_commit} HEAD"]: raise BundleError( f"materialized {label} Git bundle does not bind the expected HEAD" ) target_root = target_root.expanduser() if target_root.is_symlink(): raise BundleError(f"{label} checkout target must not be a symlink") if target_root.exists(): return require_exact_git_checkout(target_root, expected_commit, label) target_root = target_root.resolve() target_root.parent.mkdir(parents=True, exist_ok=True) if target_root.parent.is_symlink(): raise BundleError(f"{label} checkout parent must not be a symlink") with tempfile.TemporaryDirectory( prefix=f".{target_root.name}-", dir=target_root.parent, ) as temporary_name: checkout = Path(temporary_name) / "checkout" git_command( "-c", "core.hooksPath=/dev/null", "clone", "--no-checkout", "--no-local", str(bundle_path), str(checkout), ) git_command( "-c", "core.hooksPath=/dev/null", "checkout", "--detach", expected_commit, cwd=checkout, ) git_command("remote", "remove", "origin", cwd=checkout) require_exact_git_checkout(checkout, expected_commit, label) os.replace(checkout, target_root) return require_exact_git_checkout(target_root, expected_commit, label) def materialize_downstream_checkout( layout: dict[str, object], target_root: Path, *, expected_commit: str, ) -> Path: return materialize_exact_git_checkout( Path(str(layout.get("bundle_root", ""))), Path(str(layout.get("downstream_git_bundle", ""))), target_root, expected_commit=expected_commit, label="downstream", ) def materialize_official_app_packs_checkout( layout: dict[str, object], target_root: Path, *, expected_commit: str, ) -> Path: return materialize_exact_git_checkout( Path(str(layout.get("bundle_root", ""))), Path(str(layout.get("official_app_packs_git_bundle", ""))), target_root, expected_commit=expected_commit, label="official app-packs", ) def require_exact_downstream_release_pack_sources( layout: dict[str, object], source_root: Path, ) -> None: snapshot_root = Path(str(layout.get("downstream_root", ""))).expanduser() if snapshot_root.is_symlink() or not snapshot_root.is_dir(): raise BundleError("materialized downstream snapshot is missing or invalid") snapshot_root = snapshot_root.resolve() source_root = source_root.expanduser().resolve() stack_lock_path = snapshot_root / "stack.lock.json" try: stack_lock = json.loads(stack_lock_path.read_text()) except (OSError, json.JSONDecodeError) as exc: raise BundleError("materialized downstream stack.lock.json is invalid") from exc if not isinstance(stack_lock, dict): raise BundleError("materialized downstream stack.lock.json must be an object") raw_entries = stack_lock.get("packs") entries = ( [str(entry).strip() for entry in raw_entries if str(entry).strip()] if isinstance(raw_entries, list) else [] ) if not entries: pack_build = stack_lock.get("pack_build") final_pack = ( str(pack_build.get("final_pack_path") or "").strip() if isinstance(pack_build, dict) else "" ) entries = [final_pack or "packs/NOT-EDIT-final-merged-packs"] for entry in entries: if entry.startswith("official_repo:"): continue require_exact_downstream_pack_source(snapshot_root, source_root, entry) def require_exact_downstream_pack_source( snapshot_root: Path, source_root: Path, entry: str, ) -> None: relative = PurePosixPath(entry) if relative.is_absolute() or ".." in relative.parts or not relative.parts: raise BundleError(f"downstream release pack path is invalid: {entry}") snapshot_pack = snapshot_root / Path(*relative.parts) source_pack = source_root / Path(*relative.parts) if tree_sha256(snapshot_pack) != tree_sha256(source_pack): raise BundleError( "downstream release pack does not match its exact Git source: " + entry ) def verify_materialized_files( root: Path, entries: dict[str, dict[str, object]] ) -> None: actual: set[str] = set() for path in root.rglob("*"): if path.is_dir(): continue if path.name == MATERIALIZED_RECEIPT_NAME: continue if path.is_symlink() or not path.is_file(): raise BundleError(f"materialized bundle contains unsupported path: {path}") actual.add(path.relative_to(root).as_posix()) if actual != set(entries): raise BundleError("materialized bundle file set does not match manifest") for name, entry in entries.items(): path = root / Path(*PurePosixPath(name).parts) if path.stat().st_size != int(entry["size"]): raise BundleError(f"materialized bundle size mismatch: {name}") if stat.S_IMODE(path.stat().st_mode) != int(entry["mode"]): raise BundleError(f"materialized bundle mode mismatch: {name}") if sha256_file(path) != entry["sha256"]: raise BundleError(f"materialized bundle SHA-256 mismatch: {name}") def materialized_bundle_receipt( manifest: dict[str, Any], *, archive_sha256: str, manifest_sha256: str, ) -> dict[str, str]: source = manifest.get("source") layout = manifest.get("layout") if not isinstance(source, dict) or not isinstance(layout, dict): raise BundleError("deployment bundle source layout is missing") downstream_relative_path = PurePosixPath( str(layout.get("downstream_root", "")) ) if ( downstream_relative_path.is_absolute() or ".." in downstream_relative_path.parts or not downstream_relative_path.parts ): raise BundleError("bundle layout path is invalid: downstream_root") return { "contract_version": BUNDLE_RECEIPT_VERSION, "archive_sha256": require_sha256( archive_sha256, "materialized bundle archive_sha256" ), "manifest_sha256": require_sha256( manifest_sha256, "materialized bundle manifest_sha256" ), "downstream_ref": require_commit( source.get("downstream_ref"), "materialized bundle downstream_ref" ), "downstream_relative_path": downstream_relative_path.as_posix(), } def materialize_deployment_bundle( archive_path: Path, target_root: Path, *, expected: dict[str, object], ) -> dict[str, object]: archive_path = archive_path.expanduser() target_root = target_root.expanduser() if archive_path.is_symlink(): raise BundleError(f"deployment bundle archive must not be a symlink: {archive_path}") if target_root.is_symlink(): raise BundleError( f"deployment bundle materialization target must not be a symlink: {target_root}" ) archive_path = archive_path.resolve() target_root = target_root.resolve() expected_archive_sha256 = require_sha256( expected.get("archive_sha256"), "expected archive_sha256" ) if not archive_path.is_file() or archive_path.is_symlink(): raise BundleError(f"deployment bundle archive is missing or invalid: {archive_path}") if sha256_file(archive_path) != expected_archive_sha256: raise BundleError("deployment bundle archive SHA-256 does not match expected identity") with tarfile.open(archive_path, "r:") as archive: members = archive.getmembers() by_name = {member.name: member for member in members} if len(by_name) != len(members): raise BundleError("deployment bundle archive contains duplicate members") manifest_member = by_name.get(BUNDLE_MANIFEST_NAME) if manifest_member is None or not manifest_member.isfile(): raise BundleError("deployment bundle manifest is missing") manifest_bytes = member_payload(archive, manifest_member) if sha256_bytes(manifest_bytes) != require_sha256( expected.get("manifest_sha256"), "expected manifest_sha256" ): raise BundleError("deployment bundle manifest SHA-256 does not match expected identity") try: manifest_raw = json.loads(manifest_bytes) except json.JSONDecodeError as exc: raise BundleError("deployment bundle manifest is invalid JSON") from exc manifest, entries = validate_manifest(manifest_raw) require_expected_identity(manifest, expected) declared_members = { BUNDLE_MANIFEST_NAME, *(f"payload/{name}" for name in entries), } extra = sorted(set(by_name) - declared_members) missing = sorted(declared_members - set(by_name)) if extra: raise BundleError(f"undeclared archive member: {extra[0]}") if missing: raise BundleError(f"declared archive member is missing: {missing[0]}") for name in entries: member = by_name[f"payload/{name}"] entry = entries[name] if not member.isfile(): raise BundleError(f"deployment bundle member is not a regular file: {name}") if member.size != int(entry["size"]): raise BundleError(f"deployment bundle member size mismatch: {name}") expected_materialized_receipt = materialized_bundle_receipt( manifest, archive_sha256=expected_archive_sha256, manifest_sha256=sha256_bytes(manifest_bytes), ) if target_root.exists(): receipt_path = target_root / MATERIALIZED_RECEIPT_NAME if not receipt_path.is_file() or receipt_path.is_symlink(): raise BundleError("existing deployment bundle has no valid receipt") try: receipt = json.loads(receipt_path.read_text()) except (OSError, json.JSONDecodeError) as exc: raise BundleError("existing deployment bundle receipt is invalid") from exc if receipt != expected_materialized_receipt: raise BundleError("existing deployment bundle identity does not match") verify_materialized_files(target_root, entries) return materialized_layout(target_root, manifest) target_root.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory( prefix=f".{target_root.name}-", dir=target_root.parent ) as temporary_name: temporary_root = Path(temporary_name) for name, entry in entries.items(): destination = temporary_root / Path(*PurePosixPath(name).parts) destination.parent.mkdir(parents=True, exist_ok=True) payload = member_payload(archive, by_name[f"payload/{name}"]) if sha256_bytes(payload) != entry["sha256"]: raise BundleError(f"deployment bundle member SHA-256 mismatch: {name}") destination.write_bytes(payload) destination.chmod(int(entry["mode"])) verify_materialized_files(temporary_root, entries) (temporary_root / MATERIALIZED_RECEIPT_NAME).write_bytes( canonical_json_bytes(expected_materialized_receipt) ) os.replace(temporary_root, target_root) return materialized_layout(target_root, manifest) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("command", choices=("version",)) return parser.parse_args() def main() -> int: args = parse_args() if args.command == "version": print(BUNDLE_CONTRACT_VERSION) return 0 raise BundleError(f"unsupported command: {args.command}") if __name__ == "__main__": raise SystemExit(main())