@typhons/sandbox-tools 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ipc.js +1 -5
- package/package.json +1 -1
- package/relay-client.js +2 -6
- package/sandbox-watchdog.sh +1 -19
- package/session-proxy.js +1 -5
package/ipc.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
${r}
|
|
3
|
-
`),o.closeSync(c),{ok:!0}}catch(c){return c.code!=="EEXIST"?{ok:!1,error:c.message}:{ok:!1,exists:!0}}}let i=n();if(i.ok)return a(t,e),{acquired:!0,lockPath:t};let s;try{s=o.readFileSync(t,"utf8")}catch{}let u=parseInt((s||"").split(`
|
|
4
|
-
`)[0],10);if(Number.isFinite(u)&&u!==e)try{return process.kill(u,0),{acquired:!1,heldBy:u,lockPath:t}}catch(c){if(c.code==="ESRCH"){try{o.unlinkSync(t)}catch{}if(i=n(),i.ok)return a(t,e),{acquired:!0,lockPath:t,reclaimed:!0}}}return{acquired:!1,heldBy:u||"unknown",lockPath:t}}function a(t,r){let e=()=>{try{let n=o.readFileSync(t,"utf8").split(`
|
|
5
|
-
`)[0];parseInt(n,10)===r&&o.unlinkSync(t)}catch{}};process.on("exit",e),process.on("SIGINT",()=>{e(),process.exit(130)}),process.on("SIGTERM",()=>{e(),process.exit(143)})}var m=2*1024*1024,l=class{constructor(r=m){this.capacity=r,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(r){let e=this.totalBytes;return this.chunks.push({offset:e,data:Buffer.from(r)}),this.totalBytes+=r.length,this._trim(),e}sliceFrom(r){if(r>=this.totalBytes)return Buffer.alloc(0);if(r<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${r} already trimmed (trimmedTo=${this.trimmedTo})`);let e=[];for(let n of this.chunks){if(n.offset+n.data.length<=r)continue;let s=Math.max(0,r-n.offset);e.push(n.data.slice(s))}return Buffer.concat(e)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let e=this.chunks.shift();this.trimmedTo=e.offset+e.data.length}}},p=1,y=2;function E(t,r){if(!t||t.destroyed)return!1;let e=Buffer.alloc(5);return e[0]=p,e.writeUInt32LE(r.length,1),t.write(e),t.write(r),!0}function N(t,r){if(!t||t.destroyed)return!1;let e=Buffer.from(JSON.stringify(r)),n=Buffer.alloc(5);return n[0]=y,n.writeUInt32LE(e.length,1),t.write(n),t.write(e),!0}var h=class{constructor(r,e){this.onBinary=r,this.onJSON=e,this.buf=Buffer.alloc(0)}feed(r){for(this.buf=this.buf.length?Buffer.concat([this.buf,r]):r;this.buf.length>=5;){let e=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let i=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),e===p)this.onBinary(i);else if(e===y)try{this.onJSON(JSON.parse(i.toString()))}catch(s){console.error(`[ipc] bad JSON: ${s.message}`)}}}};function T(t,r){try{o.unlinkSync(t)}catch{}let e=d.createServer(r);return e.listen(t,()=>{try{o.chmodSync(t,511)}catch{}}),e}function w(t,{onConnect:r,onClose:e,onError:n,retryMs:i=500}={}){let s=null,u=!1,c=null;function f(){u||(s=d.createConnection(t),s.on("connect",()=>{r&&r(s)}),s.on("close",()=>{s=null,e&&e(),u||(c=setTimeout(f,i))}),s.on("error",S=>{n&&n(S)}))}return f(),{getSocket(){return s},close(){u=!0,c&&clearTimeout(c),s&&s.destroy()}}}function _(t,r,e,n){let i=Buffer.alloc(17);return i.write(t,0,8,"ascii"),i[8]=r,i.writeBigUInt64BE(BigInt(e),9),Buffer.concat([i,n])}function O(t){return t.length<17?null:{sessionId:t.slice(0,8).toString(),direction:t[8],offset:Number(t.readBigUInt64BE(9)),payload:t.slice(17)}}export{h as FrameParser,m as MAX_RING,l as RingBuffer,g as SOCK_PATH,x as acquireSingleInstanceLock,w as connectIPC,T as createIPCServer,O as decodeDataFrame,_ as encodeDataFrame,E as sendBinary,N as sendJSON};
|
|
1
|
+
import l from"net";import a from"fs";var g=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",y=2*1024*1024,h=class{constructor(t=y){this.capacity=t,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(t){let e=this.totalBytes;return this.chunks.push({offset:e,data:Buffer.from(t)}),this.totalBytes+=t.length,this._trim(),e}sliceFrom(t){if(t>=this.totalBytes)return Buffer.alloc(0);if(t<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${t} already trimmed (trimmedTo=${this.trimmedTo})`);let e=[];for(let n of this.chunks){if(n.offset+n.data.length<=t)continue;let i=Math.max(0,t-n.offset);e.push(n.data.slice(i))}return Buffer.concat(e)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let e=this.chunks.shift();this.trimmedTo=e.offset+e.data.length}}},d=1,p=2;function x(r,t){if(!r||r.destroyed)return!1;let e=Buffer.alloc(5);return e[0]=d,e.writeUInt32LE(t.length,1),r.write(e),r.write(t),!0}function I(r,t){if(!r||r.destroyed)return!1;let e=Buffer.from(JSON.stringify(t)),n=Buffer.alloc(5);return n[0]=p,n.writeUInt32LE(e.length,1),r.write(n),r.write(e),!0}var u=class{constructor(t,e){this.onBinary=t,this.onJSON=e,this.buf=Buffer.alloc(0)}feed(t){for(this.buf=this.buf.length?Buffer.concat([this.buf,t]):t;this.buf.length>=5;){let e=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let s=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),e===d)this.onBinary(s);else if(e===p)try{this.onJSON(JSON.parse(s.toString()))}catch(i){console.error(`[ipc] bad JSON: ${i.message}`)}}}};function w(r,t){try{a.unlinkSync(r)}catch{}let e=l.createServer(t);return e.listen(r,()=>{try{a.chmodSync(r,511)}catch{}}),e}function N(r,{onConnect:t,onClose:e,onError:n,retryMs:s=500}={}){let i=null,o=!1,c=null;function f(){o||(i=l.createConnection(r),i.on("connect",()=>{t&&t(i)}),i.on("close",()=>{i=null,e&&e(),o||(c=setTimeout(f,s))}),i.on("error",m=>{n&&n(m)}))}return f(),{getSocket(){return i},close(){o=!0,c&&clearTimeout(c),i&&i.destroy()}}}function E(r,t,e,n){let s=Buffer.alloc(17);return s.write(r,0,8,"ascii"),s[8]=t,s.writeBigUInt64BE(BigInt(e),9),Buffer.concat([s,n])}function T(r){return r.length<17?null:{sessionId:r.slice(0,8).toString(),direction:r[8],offset:Number(r.readBigUInt64BE(9)),payload:r.slice(17)}}export{u as FrameParser,y as MAX_RING,h as RingBuffer,g as SOCK_PATH,N as connectIPC,w as createIPCServer,T as decodeDataFrame,E as encodeDataFrame,x as sendBinary,I as sendJSON};
|
package/package.json
CHANGED
package/relay-client.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import g from"ws";import
|
|
3
|
-
${r}
|
|
4
|
-
`),d.closeSync(s),{ok:!0}}catch(s){return s.code!=="EEXIST"?{ok:!1,error:s.message}:{ok:!1,exists:!0}}}let c=n();if(c.ok)return B(e,t),{acquired:!0,lockPath:e};let o;try{o=d.readFileSync(e,"utf8")}catch{}let l=parseInt((o||"").split(`
|
|
5
|
-
`)[0],10);if(Number.isFinite(l)&&l!==t)try{return process.kill(l,0),{acquired:!1,heldBy:l,lockPath:e}}catch(s){if(s.code==="ESRCH"){try{d.unlinkSync(e)}catch{}if(c=n(),c.ok)return B(e,t),{acquired:!0,lockPath:e,reclaimed:!0}}}return{acquired:!1,heldBy:l||"unknown",lockPath:e}}function B(e,r){let t=()=>{try{let n=d.readFileSync(e,"utf8").split(`
|
|
6
|
-
`)[0];parseInt(n,10)===r&&d.unlinkSync(e)}catch{}};process.on("exit",t),process.on("SIGINT",()=>{t(),process.exit(130)}),process.on("SIGTERM",()=>{t(),process.exit(143)})}var Ie=2*1024*1024;var k=1,P=2;function H(e,r){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=k,t.writeUInt32LE(r.length,1),e.write(t),e.write(r),!0}function U(e,r){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(r)),n=Buffer.alloc(5);return n[0]=P,n.writeUInt32LE(t.length,1),e.write(n),e.write(t),!0}var A=class{constructor(r,t){this.onBinary=r,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(r){for(this.buf=this.buf.length?Buffer.concat([this.buf,r]):r;this.buf.length>=5;){let t=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let c=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),t===k)this.onBinary(c);else if(t===P)try{this.onJSON(JSON.parse(c.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function Y(e,{onConnect:r,onClose:t,onError:n,retryMs:c=500}={}){let o=null,l=!1,s=null;function f(){l||(o=q.createConnection(e),o.on("connect",()=>{r&&r(o)}),o.on("close",()=>{o=null,t&&t(),l||(s=setTimeout(f,c))}),o.on("error",C=>{n&&n(C)}))}return f(),{getSocket(){return o},close(){l=!0,s&&clearTimeout(s),o&&o.destroy()}}}var xe=ee(te(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var ne=process.env.RELAY_HOST||"ssh.typhons.dev",re=process.env.RELAY_SCHEME||"wss",T=process.env.E2B_RUN_DIR||"/run/e2b",oe=`${T}/.RELAY_TOKEN`,ie=`${T}/.E2B_SANDBOX_ID`,M=`${T}/.SAVED_SANDBOX_ID`,ce=`${T}/.DEVBOX_NAME`,se=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),ae=3e3,F=15e3,le=6e4,K=parseInt(process.env.RELAY_CLIENT_EVICTION_RETRY_MS||"15000",10),J=parseInt(process.env.RELAY_CLIENT_EVICTION_GIVEUP_MS||String(30*60*1e3),10),ue=parseInt(process.env.RELAY_CLIENT_ID_RECHECK_MS||"10000",10),V="@typhons/sandbox-tools",de=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",R=parseInt(process.env.KEEPALIVE_PORT||"49999");function fe(){try{return y.readFileSync(oe,"utf8").trim()}catch{}return""}function v(){try{return y.readFileSync(ie,"utf8").trim()}catch{}return"unknown"}function i(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var h="unknown";function pe(){if(process.env.RELAY_URL)return process.env.RELAY_URL;h=v();try{let e=y.readFileSync(M,"utf8").trim();if(e&&e!==h){i(`Clone detected (${e} -> ${h}), interrupting agents...`);try{I("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{y.writeFileSync(M,h)}catch{}}}catch{}return`${re}://${ne}/tunnel/${h}`}var E=null,O=null;function he(){return Y(b,{onConnect(r){i("Connected to session-proxy via IPC"),E=r,O=new A(t=>{a&&a.readyState===g.OPEN&&a.send(t)},t=>{a&&a.readyState===g.OPEN&&a.send(JSON.stringify(t))}),r.on("data",t=>O.feed(t))},onClose(){i("IPC connection to session-proxy lost, will retry..."),E=null,O=null},onError(r){r.code!=="ENOENT"&&r.code!=="ECONNREFUSED"&&i(`IPC error: ${r.message}`)}})}var a=null,L=null,u=0,_=null,m=null;function x(e=se){if(a){try{a.terminate()}catch{}a=null}_&&(clearTimeout(_),_=null),_=setTimeout(()=>{_=null,W()},e)}function W(){let e=++u,r=pe();i(`Connecting to ${r}... (gen=${e}, sandbox=${h})`);let t={},n=fe();if(n&&(t.Authorization=`Bearer ${n}`),a){try{a.terminate()}catch{}a=null}let c=new g(r,{headers:t});a=c;let o=null,l=!0,s=setTimeout(()=>{if(e===u){i(`Handshake timeout after ${F}ms \u2014 aborting and reconnecting`);try{c.terminate()}catch{}f()}},F);function f(){s&&(clearTimeout(s),s=null),o&&(clearInterval(o),o=null),e===u&&x()}function C(){l=!0,o=setInterval(()=>{if(e!==u){clearInterval(o);return}if(!l){i("No pong received \u2014 connection dead, reconnecting"),f();return}l=!1;try{c.ping()}catch{f()}},ae)}c.on("open",()=>{if(e!==u){c.terminate();return}s&&(clearTimeout(s),s=null),i(`Connected${L?` (reconnect, port ${L})`:""} (gen=${e})`),C()}),c.on("pong",()=>{e===u&&(l=!0,m!==null&&(i(`Reclaimed slot after eviction (was evicted for ${Math.round((Date.now()-m)/1e3)}s)`),m=null))}),c.on("message",(p,S)=>{if(e===u){if(!S){try{ye(JSON.parse(p.toString()))}catch(N){i(`Bad control msg: ${N.message}`)}return}E&&!E.destroyed&&H(E,Buffer.from(p))}}),c.on("close",(p,S)=>{if(e!==u)return;let N=S?S.toString():"";if(p===4100){let w=Date.now();m===null&&(m=w);let $=w-m;$>=J&&(i(`Replaced by newer connection (code 4100) \u2014 gave up after ${Math.round($/1e3)}s, exiting`),process.exit(42)),i(`Replaced by newer connection (code 4100, reason=${N}) \u2014 retrying in ${K}ms (${Math.round($/1e3)}s evicted, giveup at ${Math.round(J/1e3)}s)`),s&&(clearTimeout(s),s=null),o&&(clearInterval(o),o=null),e===u&&x(K);return}i(`Data WS closed: code=${p}, reason=${N}`),f()}),c.on("error",p=>{e===u&&(i(`Data WS error: ${p.message} (code=${p.code||"none"})`),f())}),c.on("unexpected-response",(p,S)=>{e===u&&(i(`Data WS unexpected response: status=${S.statusCode}`),f())})}function ye(e){if(e.type==="port"){L=e.port,i(`Assigned port ${e.port}`);return}if(e.type==="name"){if(i(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{Z("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),y.writeFileSync("/etc/hostname",e.name+`
|
|
7
|
-
`)}catch{}try{y.writeFileSync(ce,e.name)}catch{}}return}E&&!E.destroyed&&U(E,e)}function G(){try{let e=I(`npm view ${V} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let r="";try{r=JSON.parse(y.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==r){i(`Update available: ${r} \u2192 ${e}, installing...`),I(`npm install -g --prefix /opt/sandbox ${V}@latest`,{timeout:12e4,stdio:"inherit"});try{I('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=y.openSync("/tmp/claude-keepalive.log","a");Q("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),y.closeSync(t)}catch{}i("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function Ee(){let e=z.createServer((t,n)=>{if(n.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){n.end(JSON.stringify({ok:!0,sandboxId:v(),wsConnected:!!(a&&a.readyState===g.OPEN)}));return}if(t.method==="POST"&&t.url==="/_relay/verify"){let c="";t.on("data",o=>{c+=o,c.length>4096&&t.destroy()}),t.on("end",()=>{let o;try{o=JSON.parse(c).nonce}catch{n.writeHead(400),n.end('{"error":"bad json"}');return}if(typeof o!="string"||!o.length||o.length>256){n.writeHead(400),n.end('{"error":"missing or invalid nonce"}');return}if(!a||a.readyState!==g.OPEN){n.writeHead(503),n.end('{"error":"WS not connected"}');return}try{a.send(JSON.stringify({type:"verify-ack",nonce:o}))}catch(l){n.writeHead(500),n.end(`{"error":"${l.message}"}`);return}n.writeHead(200),n.end('{"ok":true}')}),t.on("error",()=>{try{n.writeHead(400),n.end('{"error":"req error"}')}catch{}});return}n.writeHead(404),n.end('{"error":"not found"}')}),r=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!r){r=!0,i(`Port ${R} held by another process \u2014 killing it (likely a pre-0.5.5 session-proxy that owned this port; watchdog will restart it under new code which no longer binds it)`);try{I("pkill -f /opt/sandbox/lib/node_modules/@typhons/sandbox-tools/session-proxy.js",{timeout:3e3})}catch{}setTimeout(()=>{try{e.listen(R,"0.0.0.0")}catch{}},1500);return}i(`Keepalive HTTP server error: ${t.message}`)}),e.listen(R,"0.0.0.0",()=>{i(`Keepalive HTTP server on :${R}`)}),e}function Se(){if(process.env.RELAY_URL)return;let e=v();!e||e==="unknown"||e!==h&&(i(`Sandbox ID on disk changed (${h} -> ${e}), forcing reconnect`),h=e,x())}var j=process.env.RELAY_CLIENT_LOCK_PATH||`${T}/.relay-client.lock`,X=D(j,"relay-client");X.acquired||(i(`Another relay-client is already running (pid=${X.heldBy}, lock=${j}). Refusing to start. Set RELAY_CLIENT_SKIP_LOCK=1 to override.`),process.exit(2));var we=he();Ee();W();setInterval(Se,ue);de||(setTimeout(G,1e4),setInterval(G,le));i(`Relay client started (sandbox=${v()})`);
|
|
2
|
+
import g from"ws";import J from"http";import h from"fs";import{spawn as K,execSync as _,execFileSync as M}from"child_process";import{dirname as V}from"path";import{fileURLToPath as X}from"url";import F from"net";var O=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",ae=2*1024*1024;var B=1,x=2;function b(e,r){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=B,t.writeUInt32LE(r.length,1),e.write(t),e.write(r),!0}function w(e,r){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(r)),n=Buffer.alloc(5);return n[0]=x,n.writeUInt32LE(t.length,1),e.write(n),e.write(t),!0}var T=class{constructor(r,t){this.onBinary=r,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(r){for(this.buf=this.buf.length?Buffer.concat([this.buf,r]):r;this.buf.length>=5;){let t=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let c=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),t===B)this.onBinary(c);else if(t===x)try{this.onJSON(JSON.parse(c.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function C(e,{onConnect:r,onClose:t,onError:n,retryMs:c=500}={}){let o=null,u=!1,a=null;function f(){u||(o=F.createConnection(e),o.on("connect",()=>{r&&r(o)}),o.on("close",()=>{o=null,t&&t(),u||(a=setTimeout(f,c))}),o.on("error",A=>{n&&n(A)}))}return f(),{getSocket(){return o},close(){u=!0,a&&clearTimeout(a),o&&o.destroy()}}}var _e=V(X(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var G=process.env.RELAY_HOST||"ssh.typhons.dev",j=process.env.RELAY_SCHEME||"wss",I=process.env.E2B_RUN_DIR||"/run/e2b",W=`${I}/.RELAY_TOKEN`,z=`${I}/.E2B_SANDBOX_ID`,D=`${I}/.SAVED_SANDBOX_ID`,Q=`${I}/.DEVBOX_NAME`,Z=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),q=3e3,P=15e3,ee=6e4,k=parseInt(process.env.RELAY_CLIENT_VERIFY_REJECT_RETRY_MS||"10000",10),L="@typhons/sandbox-tools",te=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",N=parseInt(process.env.KEEPALIVE_PORT||"49999");function ne(){try{return h.readFileSync(W,"utf8").trim()}catch{}return""}function $(){try{return h.readFileSync(z,"utf8").trim()}catch{}return"unknown"}function i(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var m="unknown";function re(){if(process.env.RELAY_URL)return process.env.RELAY_URL;m=$();try{let e=h.readFileSync(D,"utf8").trim();if(e&&e!==m){i(`Clone detected (${e} -> ${m}), interrupting agents...`);try{_("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{h.writeFileSync(D,m)}catch{}}}catch{}return`${j}://${G}/tunnel/${m}`}var p=null,R=null;function oe(){return C(O,{onConnect(r){i("Connected to session-proxy via IPC"),p=r,R=new T(t=>{s&&s.readyState===g.OPEN&&s.send(t)},t=>{s&&s.readyState===g.OPEN&&s.send(JSON.stringify(t))}),r.on("data",t=>R.feed(t))},onClose(){i("IPC connection to session-proxy lost, will retry..."),p=null,R=null},onError(r){r.code!=="ENOENT"&&r.code!=="ECONNREFUSED"&&i(`IPC error: ${r.message}`)}})}var s=null,v=null,l=0,S=null;function H(e=Z){if(s){try{s.terminate()}catch{}s=null}S&&(clearTimeout(S),S=null),S=setTimeout(()=>{S=null,Y()},e)}function Y(){let e=++l,r=re();i(`Connecting to ${r}... (gen=${e}, sandbox=${m})`);let t={},n=ne();if(n&&(t.Authorization=`Bearer ${n}`),s){try{s.terminate()}catch{}s=null}let c=new g(r,{headers:t});s=c;let o=null,u=!0,a=setTimeout(()=>{if(e===l){i(`Handshake timeout after ${P}ms \u2014 aborting and reconnecting`);try{c.terminate()}catch{}f()}},P);function f(){a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===l&&H()}function A(){u=!0,o=setInterval(()=>{if(e!==l){clearInterval(o);return}if(!u){i("No pong received \u2014 connection dead, reconnecting"),f();return}u=!1;try{c.ping()}catch{f()}},q)}c.on("open",()=>{if(e!==l){c.terminate();return}a&&(clearTimeout(a),a=null),i(`Connected${v?` (reconnect, port ${v})`:""} (gen=${e})`),A()}),c.on("pong",()=>{e===l&&(u=!0)}),c.on("message",(d,y)=>{if(e===l){if(!y){try{ie(JSON.parse(d.toString()))}catch(E){i(`Bad control msg: ${E.message}`)}return}p&&!p.destroyed&&b(p,Buffer.from(d))}}),c.on("close",(d,y)=>{if(e!==l)return;let E=y?y.toString():"";if(d===4100&&(i(`Replaced by newer connection (code 4100, reason=${E}) \u2014 exiting`),process.exit(42)),d===4101){i(`Identity verification rejected (code 4101, reason=${E}) \u2014 retrying in ${k}ms (likely waiting for envd to update /run/e2b/.E2B_SANDBOX_ID)`),a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===l&&H(k);return}i(`Data WS closed: code=${d}, reason=${E}`),f()}),c.on("error",d=>{e===l&&(i(`Data WS error: ${d.message} (code=${d.code||"none"})`),f())}),c.on("unexpected-response",(d,y)=>{e===l&&(i(`Data WS unexpected response: status=${y.statusCode}`),f())})}function ie(e){if(e.type==="port"){v=e.port,i(`Assigned port ${e.port}`);return}if(e.type==="name"){if(i(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{M("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),h.writeFileSync("/etc/hostname",e.name+`
|
|
3
|
+
`)}catch{}try{h.writeFileSync(Q,e.name)}catch{}}return}p&&!p.destroyed&&w(p,e)}function U(){try{let e=_(`npm view ${L} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let r="";try{r=JSON.parse(h.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==r){i(`Update available: ${r} \u2192 ${e}, installing...`),_(`npm install -g --prefix /opt/sandbox ${L}@latest`,{timeout:12e4,stdio:"inherit"});try{_('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=h.openSync("/tmp/claude-keepalive.log","a");K("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),h.closeSync(t)}catch{}i("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function ce(){let e=J.createServer((t,n)=>{if(n.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){n.end(JSON.stringify({ok:!0,sandboxId:$(),wsConnected:!!(s&&s.readyState===g.OPEN)}));return}if(t.method==="POST"&&t.url==="/_relay/verify"){let c="";t.on("data",o=>{c+=o,c.length>4096&&t.destroy()}),t.on("end",()=>{let o;try{o=JSON.parse(c).nonce}catch{n.writeHead(400),n.end('{"error":"bad json"}');return}if(typeof o!="string"||!o.length||o.length>256){n.writeHead(400),n.end('{"error":"missing or invalid nonce"}');return}if(!s||s.readyState!==g.OPEN){n.writeHead(503),n.end('{"error":"WS not connected"}');return}try{s.send(JSON.stringify({type:"verify-ack",nonce:o}))}catch(u){n.writeHead(500),n.end(`{"error":"${u.message}"}`);return}n.writeHead(200),n.end('{"ok":true}')}),t.on("error",()=>{try{n.writeHead(400),n.end('{"error":"req error"}')}catch{}});return}n.writeHead(404),n.end('{"error":"not found"}')}),r=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!r){r=!0,i(`Port ${N} held by another process \u2014 killing it (likely a pre-0.5.5 session-proxy that owned this port; watchdog will restart it under new code which no longer binds it)`);try{_("pkill -f /opt/sandbox/lib/node_modules/@typhons/sandbox-tools/session-proxy.js",{timeout:3e3})}catch{}setTimeout(()=>{try{e.listen(N,"0.0.0.0")}catch{}},1500);return}i(`Keepalive HTTP server error: ${t.message}`)}),e.listen(N,"0.0.0.0",()=>{i(`Keepalive HTTP server on :${N}`)}),e}var ge=oe();ce();Y();te||(setTimeout(U,1e4),setInterval(U,ee));i(`Relay client started (sandbox=${$()})`);
|
package/sandbox-watchdog.sh
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Watchdog: keeps both session-proxy and relay-client running.
|
|
3
|
-
#
|
|
4
|
-
# Exit codes from the children that we treat specially:
|
|
5
|
-
# 42 — relay-client gave up after EVICTION_GIVEUP_MS of consistent 4100s.
|
|
6
|
-
# Means another machine durably owns this sandbox-id slot. Stop.
|
|
7
|
-
# 2 — child refused to start because another instance already holds the
|
|
8
|
-
# single-instance lock. Keep retrying on a slower cadence (5s) so
|
|
9
|
-
# we take over cleanly if the lock-holder eventually exits, without
|
|
10
|
-
# busy-looping spawn-and-exit while it's alive.
|
|
11
3
|
|
|
12
4
|
BIN="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
|
13
5
|
export SSH_PORT="${SSH_PORT:-2222}"
|
|
@@ -19,11 +11,6 @@ log() { echo "[watchdog $(date +%H:%M:%S)] $*"; }
|
|
|
19
11
|
while true; do
|
|
20
12
|
node "$BIN/session-proxy.js" 2>&1
|
|
21
13
|
EXIT_CODE=$?
|
|
22
|
-
if [ "$EXIT_CODE" = "2" ]; then
|
|
23
|
-
log "session-proxy refused to start (lock held), retrying in 5s..."
|
|
24
|
-
sleep 5
|
|
25
|
-
continue
|
|
26
|
-
fi
|
|
27
14
|
log "session-proxy exited (code $EXIT_CODE), restarting in 2s..."
|
|
28
15
|
sleep 2
|
|
29
16
|
done
|
|
@@ -36,15 +23,10 @@ while true; do
|
|
|
36
23
|
node "$BIN/relay-client.js" 2>&1
|
|
37
24
|
EXIT_CODE=$?
|
|
38
25
|
if [ "$EXIT_CODE" = "42" ]; then
|
|
39
|
-
log "relay-client
|
|
26
|
+
log "relay-client replaced by newer connection (exit 42), stopping all"
|
|
40
27
|
kill $SESSION_PROXY_PID 2>/dev/null
|
|
41
28
|
exit 0
|
|
42
29
|
fi
|
|
43
|
-
if [ "$EXIT_CODE" = "2" ]; then
|
|
44
|
-
log "relay-client refused to start (lock held), retrying in 5s..."
|
|
45
|
-
sleep 5
|
|
46
|
-
continue
|
|
47
|
-
fi
|
|
48
30
|
log "relay-client exited (code $EXIT_CODE), restarting in 2s..."
|
|
49
31
|
sleep 2
|
|
50
32
|
done
|
package/session-proxy.js
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import M from"net";import
|
|
3
|
-
${s}
|
|
4
|
-
`),a.closeSync(u),{ok:!0}}catch(u){return u.code!=="EEXIST"?{ok:!1,error:u.message}:{ok:!1,exists:!0}}}let n=r();if(n.ok)return b(e,t),{acquired:!0,lockPath:e};let o;try{o=a.readFileSync(e,"utf8")}catch{}let i=parseInt((o||"").split(`
|
|
5
|
-
`)[0],10);if(Number.isFinite(i)&&i!==t)try{return process.kill(i,0),{acquired:!1,heldBy:i,lockPath:e}}catch(u){if(u.code==="ESRCH"){try{a.unlinkSync(e)}catch{}if(n=r(),n.ok)return b(e,t),{acquired:!0,lockPath:e,reclaimed:!0}}}return{acquired:!1,heldBy:i||"unknown",lockPath:e}}function b(e,s){let t=()=>{try{let r=a.readFileSync(e,"utf8").split(`
|
|
6
|
-
`)[0];parseInt(r,10)===s&&a.unlinkSync(e)}catch{}};process.on("exit",t),process.on("SIGINT",()=>{t(),process.exit(130)}),process.on("SIGTERM",()=>{t(),process.exit(143)})}var J=2*1024*1024,$=class{constructor(s=J){this.capacity=s,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(s){let t=this.totalBytes;return this.chunks.push({offset:t,data:Buffer.from(s)}),this.totalBytes+=s.length,this._trim(),t}sliceFrom(s){if(s>=this.totalBytes)return Buffer.alloc(0);if(s<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${s} already trimmed (trimmedTo=${this.trimmedTo})`);let t=[];for(let r of this.chunks){if(r.offset+r.data.length<=s)continue;let o=Math.max(0,s-r.offset);t.push(r.data.slice(o))}return Buffer.concat(t)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let t=this.chunks.shift();this.trimmedTo=t.offset+t.data.length}}},R=1,q=2;function A(e,s){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=R,t.writeUInt32LE(s.length,1),e.write(t),e.write(s),!0}function O(e,s){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(s)),r=Buffer.alloc(5);return r[0]=q,r.writeUInt32LE(t.length,1),e.write(r),e.write(t),!0}var w=class{constructor(s,t){this.onBinary=s,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(s){for(this.buf=this.buf.length?Buffer.concat([this.buf,s]):s;this.buf.length>=5;){let t=this.buf[0],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let n=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),t===R)this.onBinary(n);else if(t===q)try{this.onJSON(JSON.parse(n.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function F(e,s){try{a.unlinkSync(e)}catch{}let t=z.createServer(s);return t.listen(e,()=>{try{a.chmodSync(e,511)}catch{}}),t}function H(e,s,t,r){let n=Buffer.alloc(17);return n.write(e,0,8,"ascii"),n[8]=s,n.writeBigUInt64BE(BigInt(t),9),Buffer.concat([n,r])}function I(e){return e.length<17?null:{sessionId:e.slice(0,8).toString(),direction:e[8],offset:Number(e.readBigUInt64BE(9)),payload:e.slice(17)}}var B;try{B=(await import("@homebridge/node-pty-prebuilt-multiarch")).default}catch{try{B=(await import("node-pty")).default}catch{}}var Y=parseInt(process.env.SSH_PORT||"22");function c(e){console.log(`[session-proxy ${new Date().toISOString().slice(11,19)}] ${e}`)}process.on("uncaughtException",e=>{console.error(`[session-proxy] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[session-proxy] UNHANDLED REJECTION: ${e.message||e}`)});var l=new Map,d=null;function v(e,s,t,r){if(!d||d.destroyed)return!1;let n=H(e,s,t,r);return A(d,n)}function f(e){!d||d.destroyed||O(d,e)}function y(e,s){let t=l.get(e);if(t){if(c(`session ${e}: closing \u2014 ${s}`),t.socket&&!t.socket.destroyed&&t.socket.destroy(),t.ptyProcess){t.closed=!0;try{t.ptyProcess.kill()}catch{}}f({type:"close",session:e}),l.delete(e)}}function X(e,s,t){let r=l.get(e);if(!r||!(r.ptyProcess||r.socket)||r.socket&&r.socket.destroyed||r.closed)return;let o=r.user2sshd.received;if(s+t.length<=o)return;let i=t;if(s<o&&(i=t.slice(o-s)),s>o){y(e,`user2sshd stream gap: expected offset ${o}, got ${s}`);return}r.ptyProcess?r.ptyProcess.write(i):r.socket.write(i),r.user2sshd.received=Math.max(o,s+t.length),f({type:"ack",session:e,direction:"user2sshd",offset:r.user2sshd.received})}function D(e,s){let t=l.get(e);if(!t)return;let r;try{r=t.sshd2user.ring.sliceFrom(s)}catch(n){y(e,`ring buffer overflow on resend: ${n.message}`);return}r.length>0?(c(`session ${e}: re-sending ${r.length}b sshd\u2192user from offset ${s}`),v(e,0,s,r)):c(`session ${e}: relay has all ${s}b sshd\u2192user, nothing to re-send`)}function j(e){if(e.type==="open"){e.mode==="pty"?B?W(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0,e):(c(`session ${e.session}: PTY mode requested but node-pty not available \u2014 rejecting`),f({type:"close",session:e.session})):V(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0);return}if(e.type==="resize"){let s=l.get(e.session);if(s?.ptyProcess)try{s.ptyProcess.resize(e.cols,e.rows)}catch{}return}if(e.type==="close"){y(e.session,"relay requested close");return}if(e.type==="ack"){let s=l.get(e.session);if(!s)return;e.direction==="sshd2user"&&typeof e.offset=="number"&&(s.sshd2user.acked=Math.max(s.sshd2user.acked,e.offset));return}if(e.type==="get-authorized-keys"){let s=e.user||"root";if(s!=="root"&&!/^[a-z_][a-z0-9_-]{0,31}$/.test(s)){f({type:"authorized-keys",requestId:e.requestId,keys:null});return}let t=s==="root"?"/root":`/home/${s}`,r=null;try{r=K.readFileSync(`${t}/.ssh/authorized_keys`,"utf8")}catch{}f({type:"authorized-keys",requestId:e.requestId,keys:r});return}}function W(e,s,t,r){let n=l.get(e);if(n&&n.ptyProcess&&!n.closed){c(`session ${e}: re-attaching PTY (relay sshd2user=${s}, user2sshd=${t}, our sshd2user=${n.sshd2user.ring.totalBytes}, user2sshd=${n.user2sshd.received})`),n.sshd2user.acked=s,f({type:"ready",session:e,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received}),D(e,s);return}let o=r.user||"root",i=r.shell||null,u=process.env.SHELL||"/bin/bash",T=r.cols||80,P=r.rows||24,_=r.term||"xterm-256color",g,E,m;try{let h=G("getent",["passwd",o],{timeout:2e3}).toString().trim().split(":");g=parseInt(h[2]),E=parseInt(h[3]),m=h[5]||(o==="root"?"/root":`/home/${o}`)}catch{g=o==="root"?0:void 0,E=void 0,m=o==="root"?"/root":`/home/${o}`}let C=u,N=i?["-c",i]:["-l"];c(`session ${e}: spawning PTY ${C} ${N.join(" ")} as ${o} (uid=${g}, ${T}x${P})`);let x={socket:null,ptyProcess:null,closed:!1,sshd2user:{ring:new $,acked:0},user2sshd:{received:0}};l.set(e,x);try{let p=B.spawn(C,N,{name:_,cols:T,rows:P,cwd:m,env:{...process.env,TERM:_,HOME:m,USER:o,LOGNAME:o,SHELL:u,PATH:process.env.PATH},uid:g,gid:E});x.ptyProcess=p,p.onData(h=>{let S=Buffer.from(h),U=x.sshd2user.ring.append(S);v(e,0,U,S)}),p.onExit(({exitCode:h,signal:S})=>{c(`session ${e}: PTY exited (code=${h}, signal=${S})`),x.closed=!0,f({type:"exit",session:e,code:h,signal:S||void 0}),y(e,"pty exited")}),f({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})}catch(p){c(`session ${e}: PTY spawn failed: ${p.message}`),y(e,`pty spawn error: ${p.message}`)}}function V(e,s,t){let r=l.get(e);if(r&&r.socket&&!r.socket.destroyed){c(`session ${e}: re-attaching (relay sshd2user=${s}, user2sshd=${t}, our sshd2user=${r.sshd2user.ring.totalBytes}, user2sshd=${r.user2sshd.received})`),r.sshd2user.acked=s,f({type:"ready",session:e,sshd2userOffset:r.sshd2user.ring.totalBytes,user2sshdOffset:r.user2sshd.received}),D(e,s);return}c(`session ${e}: connecting to sshd :${Y}`);let n={socket:null,sshd2user:{ring:new $,acked:0},user2sshd:{received:0}};l.set(e,n);let o=M.createConnection({host:"127.0.0.1",port:Y},()=>{c(`session ${e}: sshd connected`),o.setNoDelay(!0),n.socket=o,f({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})});n.socket=o,o.on("data",i=>{let u=n.sshd2user.ring.append(i);v(e,0,u,i)}),o.on("close",()=>y(e,"sshd closed")),o.on("error",i=>y(e,`sshd error: ${i.message}`))}{let e=process.env.SESSION_PROXY_LOCK_PATH||`${k}.lock`,s=L(e,"session-proxy");s.acquired||(c(`Another session-proxy is already running (pid=${s.heldBy}, lock=${e}). Refusing to start. Set RELAY_CLIENT_SKIP_LOCK=1 to override.`),process.exit(2))}var oe=F(k,e=>{if(c("Relay-client connected via IPC"),d&&!d.destroyed&&(c("Replacing previous relay-client IPC connection"),d.destroy()),d=e,l.size>0){let t=[];for(let[r,n]of l)t.push({id:r,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received,mode:n.ptyProcess?"pty":"sshd"});O(e,{type:"existing-sessions",sessions:t})}let s=new w(t=>{let r=I(t);r&&r.direction===1&&X(r.sessionId,r.offset,r.payload)},t=>{j(t)});e.on("data",t=>s.feed(t)),e.on("close",()=>{c("Relay-client IPC disconnected"),d===e&&(d=null)}),e.on("error",t=>{c(`IPC error: ${t.message}`)})});c(`IPC server listening on ${k}`);
|
|
2
|
+
import M from"net";import q from"fs";import{execFileSync as G}from"child_process";import J from"net";import C from"fs";var P=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",Y=2*1024*1024,y=class{constructor(s=Y){this.capacity=s,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(s){let t=this.totalBytes;return this.chunks.push({offset:t,data:Buffer.from(s)}),this.totalBytes+=s.length,this._trim(),t}sliceFrom(s){if(s>=this.totalBytes)return Buffer.alloc(0);if(s<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${s} already trimmed (trimmedTo=${this.trimmedTo})`);let t=[];for(let r of this.chunks){if(r.offset+r.data.length<=s)continue;let n=Math.max(0,s-r.offset);t.push(r.data.slice(n))}return Buffer.concat(t)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let t=this.chunks.shift();this.trimmedTo=t.offset+t.data.length}}},_=1,R=2;function L(e,s){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=_,t.writeUInt32LE(s.length,1),e.write(t),e.write(s),!0}function k(e,s){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(s)),r=Buffer.alloc(5);return r[0]=R,r.writeUInt32LE(t.length,1),e.write(r),e.write(t),!0}var w=class{constructor(s,t){this.onBinary=s,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(s){for(this.buf=this.buf.length?Buffer.concat([this.buf,s]):s;this.buf.length>=5;){let t=this.buf[0],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let o=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),t===_)this.onBinary(o);else if(t===R)try{this.onJSON(JSON.parse(o.toString()))}catch(n){console.error(`[ipc] bad JSON: ${n.message}`)}}}};function D(e,s){try{C.unlinkSync(e)}catch{}let t=J.createServer(s);return t.listen(e,()=>{try{C.chmodSync(e,511)}catch{}}),t}function F(e,s,t,r){let o=Buffer.alloc(17);return o.write(e,0,8,"ascii"),o[8]=s,o.writeBigUInt64BE(BigInt(t),9),Buffer.concat([o,r])}function H(e){return e.length<17?null:{sessionId:e.slice(0,8).toString(),direction:e[8],offset:Number(e.readBigUInt64BE(9)),payload:e.slice(17)}}var x;try{x=(await import("@homebridge/node-pty-prebuilt-multiarch")).default}catch{try{x=(await import("node-pty")).default}catch{}}var U=parseInt(process.env.SSH_PORT||"22");function i(e){console.log(`[session-proxy ${new Date().toISOString().slice(11,19)}] ${e}`)}process.on("uncaughtException",e=>{console.error(`[session-proxy] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[session-proxy] UNHANDLED REJECTION: ${e.message||e}`)});var u=new Map,c=null;function v(e,s,t,r){if(!c||c.destroyed)return!1;let o=F(e,s,t,r);return L(c,o)}function d(e){!c||c.destroyed||k(c,e)}function h(e,s){let t=u.get(e);if(t){if(i(`session ${e}: closing \u2014 ${s}`),t.socket&&!t.socket.destroyed&&t.socket.destroy(),t.ptyProcess){t.closed=!0;try{t.ptyProcess.kill()}catch{}}d({type:"close",session:e}),u.delete(e)}}function j(e,s,t){let r=u.get(e);if(!r||!(r.ptyProcess||r.socket)||r.socket&&r.socket.destroyed||r.closed)return;let n=r.user2sshd.received;if(s+t.length<=n)return;let a=t;if(s<n&&(a=t.slice(n-s)),s>n){h(e,`user2sshd stream gap: expected offset ${n}, got ${s}`);return}r.ptyProcess?r.ptyProcess.write(a):r.socket.write(a),r.user2sshd.received=Math.max(n,s+t.length),d({type:"ack",session:e,direction:"user2sshd",offset:r.user2sshd.received})}function A(e,s){let t=u.get(e);if(!t)return;let r;try{r=t.sshd2user.ring.sliceFrom(s)}catch(o){h(e,`ring buffer overflow on resend: ${o.message}`);return}r.length>0?(i(`session ${e}: re-sending ${r.length}b sshd\u2192user from offset ${s}`),v(e,0,s,r)):i(`session ${e}: relay has all ${s}b sshd\u2192user, nothing to re-send`)}function K(e){if(e.type==="open"){e.mode==="pty"?x?W(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0,e):(i(`session ${e.session}: PTY mode requested but node-pty not available \u2014 rejecting`),d({type:"close",session:e.session})):X(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0);return}if(e.type==="resize"){let s=u.get(e.session);if(s?.ptyProcess)try{s.ptyProcess.resize(e.cols,e.rows)}catch{}return}if(e.type==="close"){h(e.session,"relay requested close");return}if(e.type==="ack"){let s=u.get(e.session);if(!s)return;e.direction==="sshd2user"&&typeof e.offset=="number"&&(s.sshd2user.acked=Math.max(s.sshd2user.acked,e.offset));return}if(e.type==="get-authorized-keys"){let s=e.user||"root";if(s!=="root"&&!/^[a-z_][a-z0-9_-]{0,31}$/.test(s)){d({type:"authorized-keys",requestId:e.requestId,keys:null});return}let t=s==="root"?"/root":`/home/${s}`,r=null;try{r=q.readFileSync(`${t}/.ssh/authorized_keys`,"utf8")}catch{}d({type:"authorized-keys",requestId:e.requestId,keys:r});return}}function W(e,s,t,r){let o=u.get(e);if(o&&o.ptyProcess&&!o.closed){i(`session ${e}: re-attaching PTY (relay sshd2user=${s}, user2sshd=${t}, our sshd2user=${o.sshd2user.ring.totalBytes}, user2sshd=${o.user2sshd.received})`),o.sshd2user.acked=s,d({type:"ready",session:e,sshd2userOffset:o.sshd2user.ring.totalBytes,user2sshdOffset:o.user2sshd.received}),A(e,s);return}let n=r.user||"root",a=r.shell||null,$=process.env.SHELL||"/bin/bash",E=r.cols||80,O=r.rows||24,T=r.term||"xterm-256color",m,B,g;try{let l=G("getent",["passwd",n],{timeout:2e3}).toString().trim().split(":");m=parseInt(l[2]),B=parseInt(l[3]),g=l[5]||(n==="root"?"/root":`/home/${n}`)}catch{m=n==="root"?0:void 0,B=void 0,g=n==="root"?"/root":`/home/${n}`}let b=$,N=a?["-c",a]:["-l"];i(`session ${e}: spawning PTY ${b} ${N.join(" ")} as ${n} (uid=${m}, ${E}x${O})`);let S={socket:null,ptyProcess:null,closed:!1,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};u.set(e,S);try{let f=x.spawn(b,N,{name:T,cols:E,rows:O,cwd:g,env:{...process.env,TERM:T,HOME:g,USER:n,LOGNAME:n,SHELL:$,PATH:process.env.PATH},uid:m,gid:B});S.ptyProcess=f,f.onData(l=>{let p=Buffer.from(l),z=S.sshd2user.ring.append(p);v(e,0,z,p)}),f.onExit(({exitCode:l,signal:p})=>{i(`session ${e}: PTY exited (code=${l}, signal=${p})`),S.closed=!0,d({type:"exit",session:e,code:l,signal:p||void 0}),h(e,"pty exited")}),d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})}catch(f){i(`session ${e}: PTY spawn failed: ${f.message}`),h(e,`pty spawn error: ${f.message}`)}}function X(e,s,t){let r=u.get(e);if(r&&r.socket&&!r.socket.destroyed){i(`session ${e}: re-attaching (relay sshd2user=${s}, user2sshd=${t}, our sshd2user=${r.sshd2user.ring.totalBytes}, user2sshd=${r.user2sshd.received})`),r.sshd2user.acked=s,d({type:"ready",session:e,sshd2userOffset:r.sshd2user.ring.totalBytes,user2sshdOffset:r.user2sshd.received}),A(e,s);return}i(`session ${e}: connecting to sshd :${U}`);let o={socket:null,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};u.set(e,o);let n=M.createConnection({host:"127.0.0.1",port:U},()=>{i(`session ${e}: sshd connected`),n.setNoDelay(!0),o.socket=n,d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})});o.socket=n,n.on("data",a=>{let $=o.sshd2user.ring.append(a);v(e,0,$,a)}),n.on("close",()=>h(e,"sshd closed")),n.on("error",a=>h(e,`sshd error: ${a.message}`))}var re=D(P,e=>{if(i("Relay-client connected via IPC"),c&&!c.destroyed&&(i("Replacing previous relay-client IPC connection"),c.destroy()),c=e,u.size>0){let t=[];for(let[r,o]of u)t.push({id:r,sshd2userOffset:o.sshd2user.ring.totalBytes,user2sshdOffset:o.user2sshd.received,mode:o.ptyProcess?"pty":"sshd"});k(e,{type:"existing-sessions",sessions:t})}let s=new w(t=>{let r=H(t);r&&r.direction===1&&j(r.sessionId,r.offset,r.payload)},t=>{K(t)});e.on("data",t=>s.feed(t)),e.on("close",()=>{i("Relay-client IPC disconnected"),c===e&&(c=null)}),e.on("error",t=>{i(`IPC error: ${t.message}`)})});i(`IPC server listening on ${P}`);
|