#!/usr/bin/env python3 """Opt-in disposable rootless Angor gateway integration checks. No native app changes.""" import subprocess,pathlib,json,urllib.request,urllib.error,time,tempfile,os,uuid,shlex,socket import yaml if os.environ.get('ARCHY_ALLOW_DISPOSABLE_CONTAINERS') != '1': raise SystemExit('Set ARCHY_ALLOW_DISPOSABLE_CONTAINERS=1 to run isolated test containers') manifest=yaml.safe_load((pathlib.Path(__file__).resolve().parents[2]/'apps/angor-indexer/manifest.yml').read_text())['app'] health=manifest['health_check'] health_url=health['endpoint'].rstrip('/')+health.get('path','/') run_id=uuid.uuid4().hex[:12] net='archy-angor-test-'+run_id;backend='angor-test-backend-'+run_id;gateway='angor-test-gateway-'+run_id;frontend='angor-test-frontend-'+run_id port=None def run(*a): r=subprocess.run(a,capture_output=True,text=True) if r.returncode:raise RuntimeError(r.stderr) return r.stdout.strip() def req(path,data=None,method=None,headers={}): r=urllib.request.Request(f'http://127.0.0.1:{port}'+path,data=data,method=method,headers=headers) try: with urllib.request.urlopen(r,timeout=10) as f:return f.status,f.headers,f.read() except urllib.error.HTTPError as e:return e.code,e.headers,e.read() script="""const server=require('http').createServer((q,r)=>{let b='';q.on('data',x=>b+=x);q.on('end',()=>{r.setHeader('Access-Control-Allow-Origin','https://wrong.example');if(q.url==='/api/v1/blocks/tip/height'){r.end('900000');return}r.setHeader('Content-Type','application/json');r.end(JSON.stringify({url:q.url,method:q.method,body:b,cookie:q.headers.cookie||null,auth:q.headers.authorization||null}))})});server.on('upgrade',(q,s)=>{if(q.url!=='/api/v1/ws'||q.headers.cookie||q.headers.authorization){s.destroy();return}const accept=require('crypto').createHash('sha1').update(q.headers['sec-websocket-key']+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64');s.end('HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: '+accept+'\\r\\n\\r\\n'+String.fromCharCode(129,11)+'fixture-tip','latin1')});server.listen(8999,'0.0.0.0')""" frontend_script="""require('http').createServer((q,r)=>{if(q.headers.cookie||q.headers.authorization){r.writeHead(500);r.end('credential leak');return}if(q.url==='/asset.js'){r.setHeader('Content-Type','application/javascript');r.end('window.explorer=true');return}if(q.url==='/'||q.url==='/tx/fixture'){r.setHeader('Content-Type','text/html');r.end('Mempool explorer fixture');return}r.writeHead(404);r.end('not found')}).listen(8080,'0.0.0.0')""" def start_frontend():run('podman','run','-d','--name',frontend,'--network',net,'--network-alias','mempool','--cap-drop=all','--security-opt=no-new-privileges','docker.io/library/node:24-alpine','node','-e',frontend_script) def start_backend():run('podman','run','-d','--name',backend,'--network',net,'--network-alias','mempool-api','--cap-drop=all','--security-opt=no-new-privileges','docker.io/library/node:24-alpine','node','-e',script) def ready(seconds=40): end=time.monotonic()+seconds while time.monotonic()