dpl/scripts/node-onboarding/configure-site.py
hellcat 5c9157f5d7
Some checks are pending
Docker Build and Publish / build (push) Waiting to run
feat: add reusable jump-host node onboarding runtime
2026-10-17 15:22:30 +08:00

45 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""Freeze a reusable SSH-site plan locally. No network, credentials or ledger writes."""
import argparse
import hashlib
import json
import importlib
import sys
from pathlib import Path
from batch import validate,atomic_json
from site_adapter import SiteAdapter
def configure(plan,site):
allowed={'ops_root','node_root','batch_backup_root','node_backup_root','qa_root','provider_snapshot','exit_registry','ssh_config','preserved_service','formal_socks_port','gateway_containers','client_sha256'}
if set(site)-allowed:raise ValueError('Unexpected site configuration fields')
library=importlib.import_module('paramiko')
if library.__version__!='5.0.0':raise ValueError('Use the reviewed Paramiko 5.0.0 environment')
plan=dict(plan,context={**plan['context'],**site})
validate(plan)
adapter=SiteAdapter(plan,Path('/private/tmp/dpl-validation-only')) # Creates no directory or connections.
adapter.pool.close()
base=Path(__file__).resolve().parent
plan.pop('command_profile',None)
plan['adapter']=[str(Path(sys.executable).resolve()),str(base/'command-adapter.py')]
paths=[base/x for x in ('batch.py','site_adapter.py','ssh_pool.py','acceptance.py')]+[Path(site[k]) for k in ('ssh_config','exit_registry')]
plan['runtime']={'kind':'ssh-site-v1','python':str(Path(sys.executable).resolve()),'paramiko_version':library.__version__,'files':{str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in paths}}
return plan
def main():
p=argparse.ArgumentParser(description=__doc__)
for k in ('plan','site','output'):p.add_argument('--'+k,required=True)
a=p.parse_args()
try:
output=Path(a.output)
if not output.is_absolute() or output.exists() or output.is_symlink():raise ValueError('New absolute output file required; never mutate a frozen plan')
plan=configure(json.loads(Path(a.plan).read_text()),json.loads(Path(a.site).read_text()))
atomic_json(output,plan)
print(json.dumps({'state':'configured','nodes':len(plan['nodes']),'network':False,'ledger_writes':False}))
return 0
except Exception:
print(json.dumps({'state':'blocked','reason':'Inspect explicit site paths, registry and plan; no network performed'}));return 2
if __name__=='__main__':raise SystemExit(main())