Deliver validated backup capture to worker via immutable runtime bundle
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
codex 2026-09-05 22:32:30 +02:00
parent 480d181549
commit 34b9385bee

View file

@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Deploy the reviewed backup entry point to activity-core without editing host checkouts."""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
from state_hub_preflight_lane import assert_cluster
ROOT=Path(__file__).resolve().parents[1]
FILES=['tools/cmd/forgejo-backup','scripts/capture_forgejo_archive.py','lib/railiance-backup-common.sh','lib/railiance-print.sh']
def bundle():
values={p.replace('/','__'):(ROOT/p).read_text() for p in FILES}
values['entrypoint']='#!/bin/sh\nexec /opt/railiance-backup-verified/tools/cmd/forgejo-backup "$@"\n'
digest=hashlib.sha256(json.dumps(values,sort_keys=True).encode()).hexdigest()
name='backup-verified-'+digest[:12]
cm={'apiVersion':'v1','kind':'ConfigMap','metadata':{'name':name,'namespace':'activity-core','labels':{'railiance.io/workplan':'RPF-WP-0029'}},'immutable':True,'data':values}
patch={'spec':{'template':{'spec':{'volumes':[{'name':'backup-verified','configMap':{'name':name,'defaultMode':365,'items':[{'key':p.replace('/','__'),'path':p} for p in FILES]+[{'key':'entrypoint','path':'entrypoint'}]}}], 'containers':[{'name':'worker','volumeMounts':[{'name':'backup-verified','mountPath':'/opt/railiance-backup-verified','readOnly':True},{'name':'backup-verified','mountPath':'/opt/railiance-platform/tools/cmd/forgejo-backup','subPath':'entrypoint','readOnly':True}]}]}}}}
return cm,patch
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--kubeconfig',required=True);p.add_argument('--receipt',required=True);a=p.parse_args()
k=['kubectl','--kubeconfig',a.kubeconfig]
assert_cluster(k)
cm,patch=bundle()
def cmd(args,payload=None):
r=subprocess.run(k+args,input=None if payload is None else json.dumps(payload).encode(),capture_output=True,timeout=180)
if r.returncode: raise RuntimeError('backup bundle operation failed')
return r.stdout
deployment=json.loads(cmd(['-n','activity-core','get','deployment','actcore-worker','-o','json']))
# Select the existing worker container by its declared backup hostPath mount.
selected=[c['name'] for c in deployment['spec']['template']['spec']['containers'] if any(m['mountPath']=='/opt/railiance-platform' for m in c.get('volumeMounts',[]))]
if len(selected)!=1: raise RuntimeError('ambiguous worker container')
patch['spec']['template']['spec']['containers'][0]['name']=selected[0]
cmd(['apply','--dry-run=server','-f','-'],cm)
cmd(['-n','activity-core','patch','deployment','actcore-worker','--type=strategic','--dry-run=server','--patch-file=/dev/stdin'],patch)
cmd(['apply','-f','-'],cm)
cmd(['-n','activity-core','patch','deployment','actcore-worker','--type=strategic','--patch-file=/dev/stdin'],patch)
cmd(['-n','activity-core','rollout','status','deployment/actcore-worker','--timeout=150s'])
for file in FILES:
actual=cmd(['-n','activity-core','exec','deployment/actcore-worker','-c',selected[0],'--','sha256sum','/opt/railiance-backup-verified/'+file]).split()[0].decode()
if actual!=hashlib.sha256((ROOT/file).read_bytes()).hexdigest(): raise RuntimeError('worker source mismatch')
receipt={'status':'verified','configmap':cm['metadata']['name'],'worker_ready':True,'bundle_files_match':True,'host_checkout_edited':False,'rollback':'Restore deployment from its prior ReplicaSet with kubectl rollout undo; retain immutable ConfigMap until no revision references it.'}
Path(a.receipt).write_text(json.dumps(receipt,indent=2)+'\n')
print(json.dumps(receipt))
if __name__=='__main__': main()