okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
ce3e329
main
1"""Copy a detached kernel's frames between this process's stdio and its socket. 2 3albedo_bridge.py start <run_dir> <modules-json> 4albedo_bridge.py attach <run_dir> 5albedo_bridge.py launch <run_dir> <modules-json> 6albedo_bridge.py --frame 7 8`--frame` takes the same arguments from its first stdin frame instead, 9`{"bridge": {"argv": [...], "cwd": dir}}`, and starts in `cwd` (exit status 4 10when it is no directory): the remote form, so the daemon's ssh command line 11never carries a path or the module list. 12 13The daemon talks to this process as it once talked to the kernel itself, over 144-byte-length-framed stdio, and writes the attach frame first. `start` reads 15that frame to learn the kernel's token, starts the albedo_kernel.py beside this 16file in its own session (the token travels over the kernel's stdin, never 17argv), and waits for its socket; `attach` connects to a kernel that is already running. Either way this 18process announces the bundle it runs from, then copies bytes both ways until 19one side closes. Killing it never touches the kernel. 20 21`launch` only starts the kernel, its token read as one stdin line, and exits 0 22once the socket answers: a local daemon connects to that socket itself, so no 23bridge stays running beside a local kernel. 24 25Exit status 3 means no kernel is there to attach to, so the daemon can tell a 26kernel that is gone from a connection that merely dropped. 27""" 28 29from __future__import annotations 30 31import errno 32import os 33import socket 34import subprocess 35import sys 36import threading 37import time 38from collections .abc import Callable 39 40import albedo_bundle 41import albedo_link 42 43GONE = 3 44NO_FOLDER = 4 45START_TIMEOUT = 10.0 # seconds a starting kernel has to bind its socket 46 47 48def connect (run_dir :str )-> socket .socket | None : 49"""The kernel's socket, or None when nothing listens there.""" 50connection = socket .socket (socket .AF_UNIX ,socket .SOCK_STREAM ) 51try : 52os .chdir (run_dir ) 53connection .connect (albedo_link .SOCKET ) 54return connection 55except OSError as error : 56connection .close () 57if error .errno in (errno .ENOENT ,errno .ECONNREFUSED ,errno .ENOTDIR ): 58return None 59raise 60 61 62KERNEL = os .path .join (os .path .dirname (os .path .abspath (__file__ )),"albedo_kernel.py" ) 63 64 65def start (run_dir :str ,modules :str ,token :str )-> socket .socket : 66os .makedirs (run_dir ,mode = 0o700 ,exist_ok = True ) 67with open (os .path .join (run_dir ,"kernel.log" ),"ab" )as log : 68kernel = subprocess .Popen ( 69 [sys .executable ,"-u" ,KERNEL ,"--run" ,run_dir ,modules ], 70stdin = subprocess .PIPE , 71stdout = subprocess .DEVNULL , 72stderr = log , 73start_new_session = True , 74close_fds = True , 75 ) 76assert kernel .stdin is not None 77kernel .stdin .write (token .encode ()+ b"\n" ) 78kernel .stdin .close () 79deadline = time .monotonic ()+ START_TIMEOUT 80while time .monotonic ()< deadline : 81connection = connect (run_dir ) 82if connection is not None : 83return connection 84if kernel .poll ()is not None : 85break 86time .sleep (0.02 ) 87sys .stderr .write ( 88f"albedo_bridge: kernel did not start (see { run_dir } /kernel.log)\n" 89 ) 90sys .exit (GONE ) 91 92 93def stdin (size :int )-> bytes : 94return os .read (0 ,size ) 95 96 97def stdout (data :bytes )-> int : 98return os .write (1 ,data ) 99 100 101def pump (source :Callable [[int ],bytes ],sink :Callable [[bytes ],int | None ])-> None : 102try : 103while data := source (65536 ): 104albedo_link .write_all (sink ,data ) 105except OSError : 106pass 107os ._exit (0 ) 108 109 110def framed ()-> list [str ]: 111"""The arguments a remote daemon sent as the first frame.""" 112first = albedo_link .read_frame (stdin ) 113spec = first .get ("bridge" )if isinstance (first ,dict )else None 114argv = spec .get ("argv" )if isinstance (spec ,dict )else None 115if not isinstance (argv ,list )or not all (isinstance (a ,str )for a in argv ): 116sys .exit (2 ) 117cwd = spec .get ("cwd" )if isinstance (spec ,dict )else None 118if isinstance (cwd ,str ): 119try : 120os .chdir (cwd ) 121except OSError : 122sys .exit (NO_FOLDER ) 123return [sys .argv [0 ],* argv ] 124 125 126def main (argv :list [str ])-> None : 127if argv [1 :]== ["--frame" ]: 128argv = framed () 129mode ,run_dir = argv [1 ],argv [2 ] 130if mode == "launch" : 131start (run_dir ,argv [3 ],sys .stdin .readline ().strip ()).close () 132return 133if mode == "start" : 134attach = albedo_link .read_frame (stdin ) 135if not isinstance (attach ,dict )or not isinstance (attach .get ("attach" ),dict ): 136sys .exit (2 ) 137token = attach ["attach" ].get ("token" ) 138if not isinstance (token ,str ): 139sys .exit (2 ) 140connection = start (run_dir ,argv [3 ],token ) 141first = albedo_link .encode (attach ) 142else : 143found = connect (run_dir ) 144if found is None : 145sys .exit (GONE ) 146connection ,first = found ,None 147albedo_link .write_frame ( 148stdout ,albedo_link .encode ({"bridge" : {"bundle" :albedo_bundle .digest ()}}) 149 ) 150if first is not None : 151albedo_link .write_frame (connection .send ,first ) 152threading .Thread (target = pump ,args = (stdin ,connection .send ),daemon = True ).start () 153pump (connection .recv ,stdout ) 154 155 156if __name__ == "__main__" : 157main (sys .argv )