#!/usr/bin/python3 """Actual node-specific dpkg transaction in a disposable systemd/ext4 realm.""" import hashlib import json import os from pathlib import Path import subprocess import time P=Path;PACKAGE=P('/opt/node-package-fixture');OUT=P('/evidence') assert os.geteuid()==0 and P('/run/.containerenv').exists() assert os.uname().nodename=='node-package-qualification' plan=json.loads((PACKAGE/'plan.json').read_text());baseline=json.loads((PACKAGE/'baseline.json').read_text()) def sha(path): with P(path).open('rb') as f:return hashlib.file_digest(f,'sha256').hexdigest() def run(args,name,timeout=180): p=subprocess.run(args,capture_output=True,text=True,timeout=timeout) (OUT/(name+'.json')).write_text(json.dumps({'args':args,'exit':p.returncode,'stdout':p.stdout,'stderr':p.stderr},indent=2)) p.check_returncode();return p.stdout def services(): return run(['systemctl','show','NetworkManager.service','polkit.service','dbus.service','systemd-logind.service', '-p','Id','-p','ActiveState','-p','MainPID','-p','InvocationID'], 'services-'+str(time.monotonic_ns())) assert sha('/var/lib/dpkg/status')==plan['dpkg_status_sha256']==baseline['dpkg_status_sha256'] before=sha('/var/lib/dpkg/status') versions_before=run(['dpkg-query','-W','-f=${Package}\t${Version}\t${Status}\n'],'versions-before') # Start real service daemons in this namespace. NM owns no physical interface. run(['systemctl','start','dbus.service','NetworkManager.service','polkit.service','systemd-logind.service'],'start-services') state_before=services() run(['/usr/bin/python3','-c','import sys; print(sys.version)'],'python-before') paths=[] for name,item in plan['selected'].items(): path=PACKAGE/'debs'/P(item['filename']).name assert path.stat().st_size==item['size'] and sha(path)==item['sha256'] paths.append(str(path)) assert sum(P(path).stat().st_size for path in paths)==plan['budget']['deb_exact_bytes'] # Export a boundary file to the outside observer before the first package hook. (OUT/'transaction-start.json').write_text(json.dumps({'monotonic_ns':time.monotonic_ns(),'dpkg_status_sha256':before})) started=time.monotonic() run(['dpkg','--unpack',*paths],'dpkg-unpack',timeout=300) run(['dpkg','--configure','-a'],'dpkg-configure',timeout=300) elapsed=time.monotonic()-started run(['dpkg','--audit'],'dpkg-audit') versions_after=run(['dpkg-query','-W','-f=${Package}\t${Version}\t${Status}\n'],'versions-after') parsed={line.split('\t')[0]:(line.split('\t')[1:]) for line in versions_after.splitlines()} for name,item in plan['selected'].items():assert parsed[name]==[item['version'],'install ok installed'],name run(['/usr/bin/python3','-c','from gi.repository import Gio,GLib; import ctypes; ctypes.CDLL("libpython3.13.so.1.0"); print(GLib.MAJOR_VERSION,GLib.MINOR_VERSION)'],'python-gi-after') state_after=services() for unit in ('NetworkManager.service','polkit.service','dbus.service','systemd-logind.service'): run(['systemctl','is-active',unit],'active-'+unit) run(['journalctl','--no-pager'],'all-service-journal') hooks={} for name in plan['selected']: for path in P('/var/lib/dpkg/info').glob(name+'*'): if path.suffix in ('.preinst','.postinst','.prerm','.postrm','.triggers') and path.is_file():hooks[str(path)]=sha(path) result={'passed':True,'baseline_dpkg_status_sha256':before,'after_dpkg_status_sha256':sha('/var/lib/dpkg/status'), 'selected':{name:item['version'] for name,item in plan['selected'].items()},'compressed_deb_bytes':sum(P(p).stat().st_size for p in paths), 'transaction_seconds':elapsed,'maintainer_hook_sha256':hooks,'services_before':state_before,'services_after':state_after, 'kernel_unchanged':os.uname().release,'physical_power_operation':False, 'scope':'actual package transaction against captured node dpkg database and public Python/GLib payload/hook baseline; synthetic service configuration, no installed private data'} (OUT/'transaction-result.json').write_text(json.dumps(result,indent=2)) print(json.dumps(result))