railiance-apps/tools/vergabe_pilot_operating_restore.py
tegwick bf77e378a8
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Record native recovery success and prepare attended pilot backups
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
2026-11-06 10:55:12 +01:00

120 lines
6.2 KiB
Python

"""Select a successful attended point for the already-proved isolated restore."""
import json
import os
import re
import shlex
import shutil
import subprocess
import tempfile
import uuid
from pathlib import Path
import vergabe_pilot_point as point
import vergabe_pilot_restore as restore
import vergabe_pilot_restore_remote as transport
from vergabe_pilot_recovery_store import require
def remote_command(sources, run, nonce, cleanup=False):
point.run_time(run)
require(re.fullmatch(r"[0-9a-f]{12}", nonce), "invalid_restore_nonce")
bootstrap = "import sys,types;"
for name in ("vergabe_pilot_recovery_store", "vergabe_pilot_export", "vergabe_pilot_point", "vergabe_pilot_restore"):
bootstrap += ("m=types.ModuleType(" + repr(name) + ");sys.modules[m.__name__]=m;"
"exec(compile(" + repr(sources[name]) + "," + repr(name + ".py") + ",'exec'),m.__dict__);")
selection = '''
import json,os,re,signal
from pathlib import Path
import vergabe_pilot_export as export
import vergabe_pilot_point as point
import vergabe_pilot_restore as restore
from vergabe_pilot_recovery_store import Refused,require
try:
run,nonce,mode=sys.argv[1:]
point.run_time(run)
require(os.geteuid()==0 and os.environ.get('SUDO_USER')=='tegwick','founder_node_sudo_required')
export.RUN_ID=run;export.NAME='vergabe-pilot-'+run
if mode!='cleanup':
stage=Path('/home/tegwick')/('vergabe-pilot-restore-'+nonce)
info=stage.lstat()
owner=int(os.environ['SUDO_UID'])
require(stage.is_dir() and not stage.is_symlink() and info.st_uid==owner and not info.st_mode&0o077,'private_founder_stage_required')
def read(name):
path=stage/name
info=export.regular(path,owner)
require(0<info.st_size<=16384,'restore_metadata_bound')
return json.loads(path.read_text())
origin=read('origin.json');provider=read('provider.json')
require(origin['run_id']==provider['run_id']==run,'restore_run_changed')
point.select(run,origin['files'])
require(all(isinstance(provider.get(k),str) and re.fullmatch('[0-9a-f]{64}',provider[k]) for k in ('ciphertext_sha256','plaintext_packet_sha256')),'restore_hash_shape')
restore.CIPHER=provider['ciphertext_sha256'];restore.PLAIN=provider['plaintext_packet_sha256']
def interrupted(signum,frame):raise Refused('restore_interrupted')
signal.signal(signal.SIGHUP,interrupted);signal.signal(signal.SIGTERM,interrupted)
sys.argv=['restore','--nonce',nonce,*(['--cleanup'] if mode=='cleanup' else [])]
code=restore.main()
except BaseException as error:
print(json.dumps({'status':'operating_restore_failed','failure':str(error) if isinstance(error,Refused) else 'operating_restore_error','details_suppressed':True}))
code=1
raise SystemExit(code)
'''
bootstrap += "exec(" + repr(selection) + ")"
command = shlex.join(["sudo", "/usr/bin/env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "/usr/bin/python3",
"-c", bootstrap, run, nonce, "cleanup" if cleanup else "restore"])
require(len(command.encode()) < 120000, "restore_transport_size_bound")
return ["ssh", "-t", "railiance01", command]
def selected_receipt(directory, run, sealed):
paths = list(directory.glob("provider-receipt-*.json"))
require(len(paths) <= 100, "provider_receipt_count_bound")
accepted = []
for path in paths:
body = point.read_private(path)
if point.successful_provider(body, run, sealed):
accepted.append((path, body))
require(accepted, "successful_provider_receipt_required")
path, body = max(accepted, key=lambda item: item[0].stat().st_mtime_ns)
restore.CIPHER = body["ciphertext_sha256"]
restore.PLAIN = body["plaintext_packet_sha256"]
# Full recovered ZIP/ciphertext/file verification, using the selected run.
kit, recovered, files = transport.verify_input(path)
return kit, recovered, files, path
def execute(directory, run, sealed, sources):
kit, recovered, files, receipt = selected_receipt(directory, run, sealed)
nonce = uuid.uuid4().hex[:12]
command = remote_command(sources, run, nonce)
require(subprocess.run(transport.stage_command(nonce, "create"), check=False).returncode == 0,
"restore_stage_creation_failed")
print(json.dumps({"status": "restore_transport_started", "run_id": run, "nonce": nonce}), flush=True)
with tempfile.TemporaryDirectory(prefix="restore-transfer-", dir=kit) as temporary:
temporary = Path(temporary)
for name in restore.FILES[:4]:
shutil.copy2(recovered / name, temporary / name)
shutil.copy2(receipt, temporary / "provider.json")
(temporary / "origin.json").write_text(json.dumps({"run_id": run, "files": files}))
(temporary / "origin.json").chmod(0o600)
for name in restore.FILES:
copied = subprocess.run(["scp", "-p", str(temporary / name),
"railiance01:vergabe-pilot-restore-" + nonce + "/" + name],
capture_output=True, timeout=120, check=False)
require(copied.returncode == 0, "restore_private_transfer_failed")
result = subprocess.run(command, check=False)
removed = result.returncode == 0 and subprocess.run(transport.stage_command(nonce, "cleanup"), check=False).returncode == 0
body = {"schema": "railiance.vergabe-pilot-native-restore-transport.v1", "run_id": run,
"nonce": nonce, "worker_exit_code": result.returncode, "node_stage_removed": removed,
"status": "complete" if result.returncode == 0 and removed else "requires_review"}
with os.fdopen(os.open(kit / ("native-restore-transport-" + nonce + ".json"),
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600), "w") as stream:
json.dump(body, stream, indent=2)
print(json.dumps(body, indent=2))
return 0 if body["status"] == "complete" else 1
def cleanup(sources, run, nonce):
result = subprocess.run(remote_command(sources, run, nonce, cleanup=True), check=False)
staged = subprocess.run(transport.stage_command(nonce, "cleanup"), check=False)
return 0 if result.returncode == staged.returncode == 0 else 1