Files
gti d21a9d1140 Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'
git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
2026-09-29 14:43:23 -03:00

75 lines
3.8 KiB
Python

"""Build the GTI Fiscal Agent Windows package (zip) and verify it contains no secrets.
python scripts/build_agent_package.py -> dist/GTIFiscalAgent-<version>.zip (+ .sha256)
The zip root is the install folder:
gti-agent.ps1 / gti-agent.cmd / requirements-agent.txt / agent.env.template / pilot-construcao.env.example / profiles/
agent/{src,scripts,service}, scripts/firebird_write_executor.py, central/app/{engine modules}, VERSION, MANIFEST.json
"""
import hashlib
import json
import re
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
VERSION = (ROOT / "release" / "VERSION").read_text(encoding="utf-8").strip()
NAME = f"GTIFiscalAgent-{VERSION}"
INCLUDE = [ # (source, destination inside the package)
*[(p, str(p.relative_to(ROOT)).replace("\\", "/")) for p in sorted((ROOT / "agent" / "src" / "gti_fiscal_agent").glob("*.py"))],
(ROOT / "agent" / "scripts" / "run_agent_service.py", "agent/scripts/run_agent_service.py"),
(ROOT / "agent" / "scripts" / "preflight.py", "agent/scripts/preflight.py"),
(ROOT / "agent" / "service" / "gti_agent_windows_service.py", "agent/service/gti_agent_windows_service.py"),
(ROOT / "scripts" / "firebird_write_executor.py", "scripts/firebird_write_executor.py"),
(ROOT / "central" / "__init__.py", "central/__init__.py"),
*[(ROOT / "central" / "app" / f, f"central/app/{f}") for f in ("__init__.py", "fiscal_decision.py", "dry_run_planner.py", "product_defaults.py", "icms_context.py")],
*[(p, p.name) for p in sorted((ROOT / "deploy" / "agent-windows").glob("*")) if p.is_file()],
*[(p, f"profiles/{p.name}") for p in sorted((ROOT / "deploy" / "agent-windows" / "profiles").glob("*")) if p.is_file()],
(ROOT / "release" / "VERSION", "VERSION"),
]
SECRET_PATTERNS = [
(re.compile(r"^[ ]*(AGENT_TOKEN|FB_PASSWORD|POSTGRES_PASSWORD)[ ]*=[ ]*\S+", re.M), "non-empty secret assignment"),
(re.compile(r"masterkey", re.I), "default Firebird password"),
(re.compile(r"[A-Za-z0-9_\-]{40,}"), "long token-like string"),
]
ALLOWED_LONG = re.compile(r"^[A-Za-z0-9_\-]{40,}$")
def scan(name, data):
if name.endswith((".ps1", ".cmd", ".py", ".txt", ".example", ".template", "VERSION")):
text = data.decode("utf-8", "replace")
for rx, why in SECRET_PATTERNS:
for m in rx.finditer(text):
token = m.group(0)
if why == "long token-like string" and (token.startswith(("_", "-")) or "_" in token and token.count("_") > 3 or token.isalpha()):
continue # identifiers / prose, not a secret
if why == "long token-like string" and not re.search(r"\d", token):
continue
if why == "long token-like string" and re.fullmatch(r"[0-9a-f]{64}", token):
continue # schema fingerprint (public SHA-256), not a secret
raise SystemExit(f"secret scan failed in {name}: {why}: {token[:6]}...")
def main():
dist = ROOT / "dist"
dist.mkdir(exist_ok=True)
out = dist / f"{NAME}.zip"
manifest = {"name": NAME, "version": VERSION, "files": {}}
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
for src, dst in INCLUDE:
data = Path(src).read_bytes()
assert not dst.endswith((".env", ".sqlite", ".log", ".FDB")), dst
scan(dst, data)
manifest["files"][dst] = hashlib.sha256(data).hexdigest()
z.writestr(f"{NAME}/{dst}", data)
z.writestr(f"{NAME}/MANIFEST.json", json.dumps(manifest, indent=1))
digest = hashlib.sha256(out.read_bytes()).hexdigest()
(dist / f"{NAME}.zip.sha256").write_text(f"{digest} {out.name}\n", encoding="utf-8")
print(json.dumps({"package": str(out.relative_to(ROOT)), "files": len(manifest["files"]), "sha256": digest}))
if __name__ == "__main__":
main()