import argparse, subprocess, os, time, json, re, signal
from pathlib import Path
p=argparse.ArgumentParser(); p.add_argument('--checkout',required=True);p.add_argument('--home',required=True);p.add_argument('--out',required=True);p.add_argument('--port',required=True);a=p.parse_args()
out=Path(a.out);out.mkdir(parents=True,exist_ok=True)
t0=time.monotonic(); events=[]; transcript=[]; children=[]; clients={}
def emit(s):
 print(s,flush=True);transcript.append(s);events.append([round(time.monotonic()-t0,3),'o',s+'\r\n'])
def identity(pid):
 try:
  fields=Path(f'/proc/{pid}/stat').read_text().rsplit(')',1)[1].split()
  return {'pid':pid,'state':fields[0],'startTime':fields[19]}
 except FileNotFoundError:return {'pid':pid,'state':'gone'}
def running(i):return i['state'] not in ('gone','Z')
def waitfor(fn,timeout=180):
 end=time.monotonic()+timeout
 while time.monotonic()<end:
  v=fn()
  if v:return v
  time.sleep(.2)
 raise RuntimeError('Timed out waiting for app milestone')
def start(label):
 log=open(out/(label+'.raw.log'),'w');child=subprocess.Popen(['node','apps/server/src/bin.ts','serve','--base-dir',a.home,'--host','127.0.0.1','--port',a.port],cwd=a.checkout,stdout=log,stderr=subprocess.STDOUT,env={**os.environ,'T3CODE_DEV_SEED':'0'});children.append(child)
 emit(f'Start {label}: server PID {child.pid}')
 path=out/(label+'.raw.log')
 def started():
  text=path.read_text();match=re.search(r'Relay client process started; waiting for tunnel connection\s*\{\s*pid: (\d+)',text)
  if child.poll() is not None:raise RuntimeError('Server exited before starting connector')
  return (text,int(match[1])) if match else None
 text,pid=waitfor(started);clients[pid]=identity(pid)
 if 'Stopping a relay client left by a previous T3 Code server' in text:
  emit('APP: Stopping a relay client left by a previous T3 Code server')
 emit(f'APP: Relay client process started; waiting for tunnel connection (PID {pid})')
 waitfor(lambda:path.read_text().count('Relay client tunnel connection registered') >= 4)
 time.sleep(3)
 emit('APP: Relay client tunnel connection registered')
 return child,pid,path
try:
 sha=subprocess.check_output(['git','rev-parse','HEAD'],cwd=a.checkout,text=True).strip();emit('Linux real server / staged T3 Connect / cloudflared 2026.10.4');emit('Commit '+sha)
 first,old,log1=start('initial');emit('Recorded connector identity: '+json.dumps(clients[old]));emit(f'Send SIGKILL to recorded server PID {first.pid} only');first.kill();first.wait();time.sleep(1)
 post=identity(old);emit('After crash: '+json.dumps(post));survived=running(post) and post['startTime']==clients[old]['startTime']
 emit('Old connector survived server crash: '+str(survived))
 second,new,log2=start('restart');post=identity(old);emit('After restart, old connector: '+json.dumps(post));emit('Replacement connector: '+json.dumps(identity(new)))
 cleanup='Stopping a relay client left by a previous T3 Code server' in log2.read_text()
 stopped=not running(post)
 emit('Orphan cleanup logged: '+str(cleanup));emit('Old connector stopped before replacement log: '+str(cleanup and log2.read_text().index('Stopping a relay client')<log2.read_text().index('Relay client process started')) if cleanup else 'Old connector stopped before replacement log: False')
 emit('RESULT: '+('orphan removed; replacement running' if stopped else 'orphan remains alongside replacement'))
 (out/'result.json').write_text(json.dumps({'sha':sha,'survivedCrash':survived,'oldStopped':stopped,'cleanupLogged':cleanup,'replacementRunning':running(identity(new))},indent=2))
finally:
 for child in children:
  if child.poll() is None:
   emit(f'Cleanup: SIGTERM to recorded server PID {child.pid}');child.terminate()
   try:child.wait(timeout=30)
   except subprocess.TimeoutExpired:child.kill();child.wait()
 for pid,original in clients.items():
  now=identity(pid)
  if running(now) and now.get('startTime')==original.get('startTime'):emit(f'Cleanup: SIGTERM to recorded connector PID {pid}');os.kill(pid,signal.SIGTERM)
 (out/'transcript.txt').write_text('\n'.join(transcript)+'\n')
 with open(out/'session.cast','w') as f:
  f.write(json.dumps({'version':2,'width':110,'height':36,'timestamp':int(time.time()),'env':{'TERM':'xterm-256color'}})+'\n')
  for e in events:f.write(json.dumps(e)+'\n')
