#!/usr/bin/python3 """Install a reviewed node-specific OS custody owner, without starting it. This only stages root-owned code/config/unit files. The coordinator must then produce READY from actual pinned custody before a dependent D09 owner may run. Existing differing appointments/code require reviewed replacement, not overwrite. """ import argparse import hashlib import os from pathlib import Path import subprocess from d09_store import atomic, read, secure from os_update_mount_custody import CONFIG, pin_source import json def install(appointment,expected): if os.getresuid()!=(0,0,0):raise PermissionError('root required') raw=read(appointment) if hashlib.sha256(raw).hexdigest()!=expected:raise ValueError('OS custody appointment digest differs') value=json.loads(raw) if value['node']!=os.uname().nodename:raise ValueError('wrong node custody appointment') fd=pin_source(value['source'],value['source_identity']);os.close(fd) package=Path(__file__).resolve().parent.parent pairs={str(CONFIG):raw, '/etc/systemd/system/archipelago-os-custody.service':read(package/'image-recipe/configs/archipelago-os-custody.service'), '/etc/systemd/system/archipelago-d09-lifecycle.service.d/40-os-custody.conf':read(package/'image-recipe/configs/archipelago-d09-os-custody.conf')} for name in ('os_update_mount_custody.py','os_update_custody.py','os_update_plan.py','d09_store.py','d09_wire.py','d09-wire-schema.json'): pairs['/usr/libexec/'+name]=read(package/'scripts'/name) for path,data in pairs.items(): p=Path(path) if p.exists() or p.is_symlink(): if read(p)!=data:raise ValueError('different existing custody package input: '+path) else: parent=p.parent if not parent.exists(): fd=secure(parent.parent,True) try:os.mkdir(parent.name,0o755,dir_fd=fd);os.fsync(fd) finally:os.close(fd) fd=secure(parent,True);os.close(fd) for path,data in pairs.items(): p=Path(path) if not p.exists():atomic(p,data,0o600 if p==CONFIG else 0o644) subprocess.run(['/usr/bin/systemctl','daemon-reload'],check=True) subprocess.run(['/usr/bin/systemctl','enable','archipelago-os-custody.service'],check=True) return {'installed_not_started':True,'appointment_sha256':expected, 'files':{path:hashlib.sha256(raw).hexdigest() for path,raw in pairs.items()}} if __name__=='__main__': parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--appointment',type=Path,required=True);parser.add_argument('--sha256',required=True) args=parser.parse_args();print(json.dumps(install(args.appointment,args.sha256),sort_keys=True))