RedFlag/installer/windows/verify-msi-boundary.py
Fimeg 765ec4188f publish: carry the Windows product into the projection
The tree adds the installer, service and test paths admitted by this source commit. Nothing else changes about what may cross.

Source-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8

Policy-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8

Tree-Digest: 89d6fce3828a0e0de0c8eeb41ee6750f4462217e2a04b5190297134d7f4a50c3
2026-09-09 12:47:34 -04:00

47 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Check Server package ownership and wizard tables after custody verification."""
import subprocess
import sys
def export(msi, table):
return subprocess.check_output(["msiinfo", "export", msi, table], text=True)
def rows(msi, table):
lines = export(msi, table).splitlines()
return [dict(zip(lines[0].split("\t"), line.split("\t"))) for line in lines[3:] if line]
def verify(msi):
properties = {row["Property"]: row["Value"] for row in rows(msi, "Property")}
assert properties["ProductName"] == "RedFlag Server", "wrong product"
assert properties["UpgradeCode"].lower().strip("{}") == "8f3c9e6a-4b1d-4a7f-9c2e-1d6a8b2f5e91", "Server upgrade identity changed"
files = {row["FileName"].split("|")[-1] for row in rows(msi, "File")}
assert files == {"redflag-server.exe", "redflag.env.example"}, f"unexpected Server payload: {files}"
assert {row["Feature"] for row in rows(msi, "Feature")} == {"ServerFeature"}, "unexpected product mode"
components = {row["Component"]: row for row in rows(msi, "Component")}
assert int(components["ServerBinary"]["Attributes"]) & 256, "Server component is not 64-bit"
services = rows(msi, "ServiceInstall")
assert len(services) == 1 and services[0]["Name"] == "RedFlagServer", "unexpected service ownership"
controls = rows(msi, "ServiceControl")
assert len(controls) == 1 and controls[0]["Name"] == "RedFlagServer", "unexpected service control"
events = int(controls[0]["Event"])
assert not events & (1 | 16), "MSI must not start an unconfigured service"
assert events & (2 | 32 | 128) == (2 | 32 | 128), "stop/remove lifecycle missing"
dialogs = {row["Dialog"] for row in rows(msi, "Dialog")}
assert {"ServerWelcome", "ServerLicense", "ServerMaintenance", "ServerProgress", "ServerComplete", "ServerFailed", "ServerCancelled"} <= dialogs, "wizard incomplete"
controls_text = export(msi, "Control")
assert "GNU AFFERO GENERAL PUBLIC LICENSE" in controls_text, "full license text missing"
assert "How to Apply These Terms to Your New Programs" in controls_text, "license text truncated"
summary = subprocess.check_output(["msiinfo", "suminfo", msi], text=True)
assert "x64;" in summary, "Server AMD64 payload needs an x64 MSI"
print("Server package boundary verified; Windows UI and installation still require native acceptance.")
if __name__ == "__main__":
try:
verify(sys.argv[1])
except (AssertionError, KeyError, ValueError, subprocess.CalledProcessError) as error:
sys.exit(f"Server package boundary failed: {error}")