90 lines
5.3 KiB
Python
90 lines
5.3 KiB
Python
#!/usr/bin/python3 -I -S
|
|
"""One independently delegated, exact-current-generation D09 wake activation.
|
|
|
|
No APPLY, rollback, peer-selected paths, old grants or retry loop. This journal
|
|
is outside D09's executable/frontend/workspace rollback objects. An uncertain
|
|
dispatch leaves its exclusive intent and requires exact reconciliation.
|
|
"""
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
import stat
|
|
import sys
|
|
import time
|
|
|
|
ROOT=Path('/var/lib/archipelago-c01-power')
|
|
CONFIG=Path('/etc/archipelago-c01/power.json')
|
|
|
|
def checked(path):
|
|
for item in (path,*path.parents):
|
|
meta=item.lstat()
|
|
if meta.st_uid or meta.st_mode&0o022 or stat.S_ISLNK(meta.st_mode):
|
|
raise ValueError('reactivation source custody')
|
|
|
|
def main():
|
|
if os.getresuid()!=(0,0,0) or len(sys.argv)!=1:raise ValueError('root fixed reactivation entry required')
|
|
checked(CONFIG);checked(ROOT);checked(Path(__file__))
|
|
policy=json.loads(CONFIG.read_bytes())
|
|
delegation=policy['lifecycle_issuer']
|
|
if delegation['scope']!='reactivate-current-generation' or delegation['program_sha256']!=hashlib.sha256(Path(__file__).read_bytes()).hexdigest():
|
|
raise ValueError('lifecycle issuer not appointed')
|
|
state=json.loads((ROOT/'state.json').read_bytes())
|
|
if state['phase']!='RecoveryRequired' or state['configuration_sha256']!=hashlib.sha256(CONFIG.read_bytes()).hexdigest():
|
|
raise ValueError('no fresh power return boundary')
|
|
group=Path('/proc/self/cgroup').read_text().strip().split(':',2)[2].split('/')[-1]
|
|
if group not in (state['target']+'.service','archipelago-c01-power-abort@'+state['target']+'.service',
|
|
'archipelago-c01-power-observer.service'):
|
|
raise ValueError('lifecycle issuer outside returned power unit')
|
|
parent=Path(__file__).parent
|
|
for name in ('archipelago-d09-provision','d09-lifecycle-owner.py','d09_store.py','d09_wire.py','d09-wire-schema.json'):
|
|
checked(parent/name)
|
|
spec=importlib.util.spec_from_file_location('power_d09_provision',parent/'archipelago-d09-provision')
|
|
if spec is None:
|
|
from importlib.machinery import SourceFileLoader
|
|
spec=importlib.util.spec_from_loader('power_d09_provision',SourceFileLoader('power_d09_provision',str(parent/'archipelago-d09-provision')))
|
|
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
|
owner=module.owner
|
|
raw=owner.read(owner.ROOT/'ledger.json');value=json.loads(raw)
|
|
deadline=time.monotonic()+10
|
|
while (value.get('prerequisite') or {}).get('state')=='START_INTENT':
|
|
if time.monotonic()>=deadline:raise ValueError('D09 has not reconciled the prior start; no grant issued')
|
|
time.sleep(.1);raw=owner.read(owner.ROOT/'ledger.json');value=json.loads(raw)
|
|
binding=value.get('active_binding') or json.loads(owner.read(owner.BINDING))
|
|
receipt=value['receipt']
|
|
if receipt is not None and receipt['phase'] not in ('COMPLETE','AWAITING_OBSERVATION'):
|
|
raise ValueError('pending D09 operation needs independent reconciliation')
|
|
index={key:binding[key] for key in ('target_binding_digest','composition_digest','transaction_id')}
|
|
# Production's actual process/job/cgroup drain verifier. This method is
|
|
# stateless; no invented Owner instance, receipt or healthy-process result.
|
|
disposition=owner.Owner.drained_disposition(None,value)
|
|
record=dict(index,schema_version=1,allowed_action='REACTIVATE_PREREQUISITE' if receipt is None else 'REACTIVATE_RETAINED',
|
|
predecessor_epoch_id=value['epoch'],head_sequence=value['sequence'],observed_boot_id=owner.boot_id(),
|
|
preserved_head_digest=owner.digest(owner.canonical(receipt)) if receipt else owner.digest(raw),
|
|
preserved_journal_digest=owner.digest(owner.canonical({'receipts':value['journal'],'intents':value['intents']})),
|
|
preserved_consumed_history_digest=owner.digest(owner.canonical(owner.consumption_history(value))),
|
|
preserved_target_index_digest=owner.digest(owner.canonical(index)),disposition_digest=disposition,
|
|
new_epoch_id=secrets.token_hex(16),new_grant_digest=secrets.token_hex(32),execution_grant_digest=secrets.token_hex(32),
|
|
external_history_sequence=state['sequence'])
|
|
history=owner.canonical(dict(record,sequence=state['sequence'],issuer_id=delegation['id'],power_nonce=state['nonce']))
|
|
record['external_history_digest']=owner.digest(history)
|
|
intent=ROOT/f"reactivation-{state['sequence']}.intent"
|
|
fd=os.open(intent,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_CLOEXEC,0o600)
|
|
try:
|
|
encoded=owner.canonical(record)
|
|
if os.write(fd,encoded)!=len(encoded):raise ValueError('reactivation intent write uncertain')
|
|
os.fsync(fd)
|
|
finally:os.close(fd)
|
|
parent_fd=os.open(ROOT,os.O_RDONLY|os.O_DIRECTORY|os.O_CLOEXEC)
|
|
try:os.fsync(parent_fd)
|
|
finally:os.close(parent_fd)
|
|
history_path=ROOT/f"reactivation-{state['sequence']}.history"
|
|
module.atomic(history_path,history)
|
|
module.readmit(intent,history_path,activation=True)
|
|
module.atomic(ROOT/f"reactivation-{state['sequence']}.submitted",owner.canonical({'record_sha256':owner.digest(encoded)}))
|
|
print(json.dumps({'submitted':True,'record_sha256':owner.digest(encoded),'power_sequence':state['sequence']}))
|
|
|
|
if __name__=='__main__':main()
|