git-subtree-dir: fiscal git-subtree-mainline:d95165fc8fgit-subtree-split:102ba84500
75 lines
3.8 KiB
Python
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()
|