134 lines
7.7 KiB
Python
134 lines
7.7 KiB
Python
"""Apply only the explicitly authorized fixed administration delta; never install schema."""
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import zipfile
|
|
from portal_diagnostics_deployment_guard import assert_diagnostics_off
|
|
|
|
R = Path(__file__).resolve().parent.parent
|
|
assert str(R).lower().replace("\\", "/") == "c:/laragon/www/portal-investidor"
|
|
assert_diagnostics_off(R)
|
|
O = R / "outputs/investmoz-internal-designation-20261011"
|
|
P = R / "storage/app/private/investmoz-v2/portal-audit"
|
|
load = lambda p: json.loads(p.read_text(encoding="utf-8-sig"))
|
|
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
|
|
m = load(O / "manifesto.json")
|
|
assert sha(O / "manifesto.json") == "d42336c3aafcc6919bbec3263860ce2a850c4bdb831bf64003967a7de68b8c95"
|
|
archive = O / m["package"]
|
|
assert sha(archive) == "c31537ce1d17a1670784076e10e3e0d2ec89007be919ad9e6712119651af8cee"
|
|
expected = {x["path"]: x["after_sha256"] for x in m["files"]} | {x["path"]: x["sha256"] for x in m["assets"]}
|
|
assert len(m["files"]) == 16 and len(m["assets"]) == 159 and len(expected) == 175
|
|
assert all(x.startswith(("app/", "config/", "routes/", "resources/", "public/build/")) for x in expected)
|
|
assert "public/build/manifest.json" in expected
|
|
action = sys.argv[1]
|
|
record = O / "disponibilizacao-codigo.json"
|
|
|
|
def absolute(relative):
|
|
p = R / relative
|
|
assert not Path(relative).is_absolute() and ":" not in relative and ".." not in Path(relative).parts
|
|
assert p.resolve().is_relative_to(R.resolve())
|
|
return p
|
|
|
|
def write_atomic(path, data):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = path.with_name(path.name + ".owner-admin-deployment-tmp")
|
|
assert not temporary.exists()
|
|
temporary.write_bytes(data)
|
|
os.replace(temporary, path)
|
|
|
|
def env_flag(data, enabled):
|
|
# Touch one known flag only; all keys/cookies/connection/other flags remain byte-identical.
|
|
pattern = rb"(?m)^PORTAL_PROFILE_ADMINISTRATION_ENABLED=[^\r\n]*"
|
|
matches = list(re.finditer(pattern, data))
|
|
assert len(matches) <= 1
|
|
value = b"PORTAL_PROFILE_ADMINISTRATION_ENABLED=" + (b"true" if enabled else b"false")
|
|
if matches:
|
|
return re.sub(pattern, lambda _: value, data)
|
|
newline = b"\r\n" if b"\r\n" in data else b"\n"
|
|
return data + (b"" if data.endswith(b"\n") else newline) + value + newline
|
|
|
|
if action == "--deploy":
|
|
assert not record.exists(), "Preserve the prior release receipt; do not repeat blindly."
|
|
pre = load(O / "disponibilizacao-preflight.json")
|
|
assert pre["result"] == "ready_for_authorized_code_and_administration_only"
|
|
assert pre["revision"] == "2876cdfa900a"
|
|
assert (datetime.now(timezone.utc) - datetime.fromisoformat(pre["at"])).total_seconds() < 300
|
|
assert pre["backup_permanent_source_data_unchanged"] and pre["source_hashes_match"]
|
|
assert datetime.now(timezone.utc) < datetime.fromisoformat(pre["backup_valid_until"])
|
|
original = {}
|
|
for f in m["files"]:
|
|
p = absolute(f["path"])
|
|
actual = sha(p) if p.is_file() else None
|
|
assert actual == f["before_sha256"], "Destination changed: " + f["path"]
|
|
original[f["path"]] = actual
|
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
stage = P / ("owner-administration-release-" + stamp)
|
|
assert stage.parent.resolve() == P.resolve() and not stage.exists()
|
|
stage.mkdir()
|
|
restore = stage / "before"
|
|
for relative in [*original, "public/build/manifest.json", ".env"]:
|
|
source = absolute(relative)
|
|
if source.is_file():
|
|
dest = restore / relative
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(source, dest)
|
|
old_env = (R / ".env").read_bytes()
|
|
with zipfile.ZipFile(archive) as z:
|
|
assert set(z.namelist()) == set(expected) and len(z.namelist()) == len(expected)
|
|
for relative, digest in expected.items():
|
|
dest = stage / "candidate" / relative
|
|
assert dest.resolve().is_relative_to((stage / "candidate").resolve())
|
|
payload = z.read(relative)
|
|
assert hashlib.sha256(payload).hexdigest() == digest
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
dest.write_bytes(payload)
|
|
if relative.startswith("public/build/") and relative != "public/build/manifest.json":
|
|
active_asset = absolute(relative)
|
|
assert not active_asset.is_file() or sha(active_asset) == digest, "Immutable asset collision"
|
|
assert_diagnostics_off(R, payload={p:(stage / 'candidate' / p).read_bytes() for p in expected})
|
|
receipt = {
|
|
"at": datetime.now(timezone.utc).isoformat(), "revision": "2876cdfa900a",
|
|
"manifest_sha256": sha(O / "manifesto.json"), "package_sha256": sha(archive),
|
|
"destination": "https://portal-investidor.test", "private_reversal_directory": str(restore),
|
|
"before_source_hashes": original, "before_environment_sha256": hashlib.sha256(old_env).hexdigest(),
|
|
"before_build_manifest_sha256": sha(restore / "public/build/manifest.json"),
|
|
"new_sources": [p for p, h in original.items() if h is None],
|
|
"authorization": "Explicit administration-only authorization for fixed revision in this turn",
|
|
"new_tables": 0, "new_columns": 0, "accounts_organizations_representations_grants_created": 0,
|
|
"migrations_applied": [], "services_changed": False, "COUNTRY02_preserved": True,
|
|
"workflow_enabled": False, "result": "applying_with_administration_disabled",
|
|
}
|
|
record.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
write_atomic(R / ".env", env_flag(old_env, False))
|
|
order = sorted(expected, key=lambda p: (2 if p == "public/build/manifest.json" else 0 if p.startswith("public/build/") else 1, p))
|
|
for relative in order:
|
|
write_atomic(absolute(relative), (stage / "candidate" / relative).read_bytes())
|
|
assert all(sha(absolute(p)) == h for p, h in expected.items())
|
|
assert_diagnostics_off(R)
|
|
assert (R / ".env").read_bytes() == env_flag(old_env, False)
|
|
receipt.update(result="code_and_bundle_deployed_administration_OFF", completed_at=datetime.now(timezone.utc).isoformat(),
|
|
source_files_verified=16, compiled_files_verified=159, old_hashed_resources_retained=True)
|
|
record.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
print(json.dumps({k: receipt[k] for k in ["result","revision","source_files_verified","compiled_files_verified","migrations_applied","services_changed","COUNTRY02_preserved","workflow_enabled"]}))
|
|
elif action == "--enable":
|
|
receipt = load(record)
|
|
assert receipt["result"] == "code_and_bundle_deployed_administration_OFF"
|
|
assert all(sha(absolute(p)) == h for p, h in expected.items())
|
|
restore = Path(receipt["private_reversal_directory"])
|
|
assert restore.resolve().is_relative_to(P.resolve())
|
|
before = (restore / ".env").read_bytes()
|
|
assert (R / ".env").read_bytes() == env_flag(before, False)
|
|
write_atomic(R / ".env", env_flag(before, True))
|
|
receipt.update(result="administration_ON_workflow_OFF_country_preserved", enabled_at=datetime.now(timezone.utc).isoformat(),
|
|
enabled_environment_sha256=sha(R / ".env"), configuration_keys_changed=["PORTAL_PROFILE_ADMINISTRATION_ENABLED"],
|
|
authenticated_owner_interface_verification="pending owner session/MFA; not inferred from deployment")
|
|
record.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
print(json.dumps({"result":receipt["result"],"revision":receipt["revision"],"configuration_keys_changed":receipt["configuration_keys_changed"],"migrations_applied":[],"new_tables":0,"new_columns":0}))
|
|
else:
|
|
raise RuntimeError("Supported actions: --deploy, --enable")
|