@typhons/sandbox-tools 0.5.10 → 0.5.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typhons/sandbox-tools",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sandbox-relay-client": "./relay-client.js",
@@ -9,7 +9,8 @@
9
9
  "sandbox-start-all": "./sandbox-start-all.sh",
10
10
  "check-claude-active": "./check-claude-active.js",
11
11
  "stop-agents": "./stop-agents.js",
12
- "sandbox-clone": "./clone.js"
12
+ "sandbox-clone": "./clone.js",
13
+ "typhons-share-mount": "./typhons-share-mount.js"
13
14
  },
14
15
  "dependencies": {
15
16
  "ws": "^8.17.0",
package/relay-client.js CHANGED
@@ -1,6 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import T from"ws";import G from"http";import p from"fs";import{spawn as W,execSync as _,execFileSync as z}from"child_process";import{dirname as Q}from"path";import{fileURLToPath as Z}from"url";import V from"net";var O=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",he=2*1024*1024;var b=1,B=2;function D(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=b,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function C(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),o=Buffer.alloc(5);return o[0]=B,o.writeUInt32LE(t.length,1),e.write(o),e.write(t),!0}var N=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],o=this.buf.readUInt32LE(1);if(this.buf.length<5+o)break;let i=this.buf.slice(5,5+o);if(this.buf=this.buf.slice(5+o),t===b)this.onBinary(i);else if(t===B)try{this.onJSON(JSON.parse(i.toString()))}catch(r){console.error(`[ipc] bad JSON: ${r.message}`)}}}};function k(e,{onConnect:n,onClose:t,onError:o,retryMs:i=500}={}){let r=null,l=!1,a=null;function u(){l||(r=V.createConnection(e),r.on("connect",()=>{n&&n(r)}),r.on("close",()=>{r=null,t&&t(),l||(a=setTimeout(u,i))}),r.on("error",A=>{o&&o(A)}))}return u(),{getSocket(){return r},close(){l=!0,a&&clearTimeout(a),r&&r.destroy()}}}import y from"fs";import R from"path";var L="# typhons-managed-hostname";function X(e){let n=e.replace(/\.local$/,"");return`127.0.1.1 ${n===e?e:`${e} ${n}`} ${L}`}function j(e,n,t){let o=R.dirname(e),i=R.basename(e),r=R.join(o,`.${i}.tmp.${process.pid}.${Date.now()}`);try{y.writeFileSync(r,n,{mode:t})}catch{try{y.unlinkSync(r)}catch{}return!1}try{return y.renameSync(r,e),!0}catch{try{y.unlinkSync(r)}catch{}return!1}}function H(e,n="/etc/hosts"){let t=X(e),o="",i=420;try{o=y.readFileSync(n,"utf8"),i=y.statSync(n).mode&511}catch{}let r=o.split(`
3
- `);r.length>0&&r[r.length-1]===""&&r.pop();let l=r.filter(u=>!u.includes(L));l.push(t);let a=l.join(`
2
+ import A from"ws";import $e from"http";import a from"fs";import N from"path";import{spawn as se,execSync as $,execFileSync as Ae}from"child_process";import{dirname as ie}from"path";import{fileURLToPath as ae}from"url";import ye from"net";var F=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",Ye=2*1024*1024;var M=1,Y=2;function J(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=M,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function K(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),r=Buffer.alloc(5);return r[0]=Y,r.writeUInt32LE(t.length,1),e.write(r),e.write(t),!0}var R=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let c=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),t===M)this.onBinary(c);else if(t===Y)try{this.onJSON(JSON.parse(c.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function j(e,{onConnect:n,onClose:t,onError:r,retryMs:c=500}={}){let o=null,l=!1,i=null;function d(){l||(o=ye.createConnection(e),o.on("connect",()=>{n&&n(o)}),o.on("close",()=>{o=null,t&&t(),l||(i=setTimeout(d,c))}),o.on("error",_=>{r&&r(_)}))}return d(),{getSocket(){return o},close(){l=!0,i&&clearTimeout(i),o&&o.destroy()}}}import E from"fs";import w from"path";var W="# typhons-managed-hostname";function me(e){let n=e.replace(/\.local$/,"");return`127.0.1.1 ${n===e?e:`${e} ${n}`} ${W}`}function Se(e,n,t){let r=w.dirname(e),c=w.basename(e),o=w.join(r,`.${c}.tmp.${process.pid}.${Date.now()}`);try{E.writeFileSync(o,n,{mode:t})}catch{try{E.unlinkSync(o)}catch{}return!1}try{return E.renameSync(o,e),!0}catch{try{E.unlinkSync(o)}catch{}return!1}}function G(e,n="/etc/hosts"){let t=me(e),r="",c=420;try{r=E.readFileSync(n,"utf8"),c=E.statSync(n).mode&511}catch{}let o=r.split(`
3
+ `);o.length>0&&o[o.length-1]===""&&o.pop();let l=o.filter(d=>!d.includes(W));l.push(t);let i=l.join(`
4
4
  `)+`
5
- `;return a===o?!1:(j(n,a,i)||y.writeFileSync(n,a,{mode:i}),!0)}var xe=Q(Z(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 q=process.env.RELAY_HOST||"ssh.typhons.dev",ee=process.env.RELAY_SCHEME||"wss",I=process.env.E2B_RUN_DIR||"/run/e2b",te=`${I}/.RELAY_TOKEN`,ne=`${I}/.E2B_SANDBOX_ID`,P=`${I}/.SAVED_SANDBOX_ID`,re=`${I}/.DEVBOX_NAME`,oe=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),ie=3e3,U=15e3,ce=6e4,F=parseInt(process.env.RELAY_CLIENT_VERIFY_REJECT_RETRY_MS||"10000",10),Y="@typhons/sandbox-tools",se=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",$=parseInt(process.env.KEEPALIVE_PORT||"49999");function ae(){try{return p.readFileSync(te,"utf8").trim()}catch{}return""}function x(){try{return p.readFileSync(ne,"utf8").trim()}catch{}return"unknown"}function c(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var E="unknown";function le(){if(process.env.RELAY_URL)return process.env.RELAY_URL;E=x();try{let e=p.readFileSync(P,"utf8").trim();if(e&&e!==E){c(`Clone detected (${e} -> ${E}), interrupting agents...`);try{_("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{p.writeFileSync(P,E)}catch{}}}catch{}return`${ee}://${q}/tunnel/${E}`}var h=null,v=null;function ue(){return k(O,{onConnect(n){c("Connected to session-proxy via IPC"),h=n,v=new N(t=>{s&&s.readyState===T.OPEN&&s.send(t)},t=>{s&&s.readyState===T.OPEN&&s.send(JSON.stringify(t))}),n.on("data",t=>v.feed(t))},onClose(){c("IPC connection to session-proxy lost, will retry..."),h=null,v=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&c(`IPC error: ${n.message}`)}})}var s=null,w=null,d=0,g=null;function J(e=oe){if(s){try{s.terminate()}catch{}s=null}g&&(clearTimeout(g),g=null),g=setTimeout(()=>{g=null,M()},e)}function M(){let e=++d,n=le();c(`Connecting to ${n}... (gen=${e}, sandbox=${E})`);let t={},o=ae();if(o&&(t.Authorization=`Bearer ${o}`),s){try{s.terminate()}catch{}s=null}let i=new T(n,{headers:t});s=i;let r=null,l=!0,a=setTimeout(()=>{if(e===d){c(`Handshake timeout after ${U}ms \u2014 aborting and reconnecting`);try{i.terminate()}catch{}u()}},U);function u(){a&&(clearTimeout(a),a=null),r&&(clearInterval(r),r=null),e===d&&J()}function A(){l=!0,r=setInterval(()=>{if(e!==d){clearInterval(r);return}if(!l){c("No pong received \u2014 connection dead, reconnecting"),u();return}l=!1;try{i.ping()}catch{u()}},ie)}i.on("open",()=>{if(e!==d){i.terminate();return}a&&(clearTimeout(a),a=null),c(`Connected${w?` (reconnect, port ${w})`:""} (gen=${e})`),A()}),i.on("pong",()=>{e===d&&(l=!0)}),i.on("message",(f,m)=>{if(e===d){if(!m){try{de(JSON.parse(f.toString()))}catch(S){c(`Bad control msg: ${S.message}`)}return}h&&!h.destroyed&&D(h,Buffer.from(f))}}),i.on("close",(f,m)=>{if(e!==d)return;let S=m?m.toString():"";if(f===4100&&(c(`Replaced by newer connection (code 4100, reason=${S}) \u2014 exiting`),process.exit(42)),f===4101){c(`Identity verification rejected (code 4101, reason=${S}) \u2014 retrying in ${F}ms (likely waiting for envd to update /run/e2b/.E2B_SANDBOX_ID)`),a&&(clearTimeout(a),a=null),r&&(clearInterval(r),r=null),e===d&&J(F);return}c(`Data WS closed: code=${f}, reason=${S}`),u()}),i.on("error",f=>{e===d&&(c(`Data WS error: ${f.message} (code=${f.code||"none"})`),u())}),i.on("unexpected-response",(f,m)=>{e===d&&(c(`Data WS unexpected response: status=${m.statusCode}`),u())})}function de(e){if(e.type==="port"){w=e.port,c(`Assigned port ${e.port}`);return}if(e.type==="name"){if(c(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){let n=`${e.name}.local`;try{z("hostname",[n],{timeout:5e3,stdio:"ignore"}),p.writeFileSync("/etc/hostname",n+`
6
- `)}catch{}try{H(n)&&c(`Updated /etc/hosts entry for ${n}`)}catch(t){c(`Failed to update /etc/hosts: ${t.message}`)}try{p.writeFileSync(re,e.name)}catch{}}return}h&&!h.destroyed&&C(h,e)}function K(){try{let e=_(`npm view ${Y} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(p.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){c(`Update available: ${n} \u2192 ${e}, installing...`),_(`npm install -g --prefix /opt/sandbox ${Y}@latest`,{timeout:12e4,stdio:"inherit"});try{_('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=p.openSync("/tmp/claude-keepalive.log","a");W("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),p.closeSync(t)}catch{}c("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function fe(){let e=G.createServer((t,o)=>{if(o.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){o.end(JSON.stringify({ok:!0,sandboxId:x(),wsConnected:!!(s&&s.readyState===T.OPEN)}));return}if(t.method==="POST"&&t.url==="/_relay/verify"){let i="";t.on("data",r=>{i+=r,i.length>4096&&t.destroy()}),t.on("end",()=>{let r;try{r=JSON.parse(i).nonce}catch{o.writeHead(400),o.end('{"error":"bad json"}');return}if(typeof r!="string"||!r.length||r.length>256){o.writeHead(400),o.end('{"error":"missing or invalid nonce"}');return}if(!s||s.readyState!==T.OPEN){o.writeHead(503),o.end('{"error":"WS not connected"}');return}try{s.send(JSON.stringify({type:"verify-ack",nonce:r}))}catch(l){o.writeHead(500),o.end(`{"error":"${l.message}"}`);return}o.writeHead(200),o.end('{"ok":true}')}),t.on("error",()=>{try{o.writeHead(400),o.end('{"error":"req error"}')}catch{}});return}o.writeHead(404),o.end('{"error":"not found"}')}),n=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!n){n=!0,c(`Port ${$} 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($,"0.0.0.0")}catch{}},1500);return}c(`Keepalive HTTP server error: ${t.message}`)}),e.listen($,"0.0.0.0",()=>{c(`Keepalive HTTP server on :${$}`)}),e}var Oe=ue();fe();M();se||(setTimeout(K,1e4),setInterval(K,ce));c(`Relay client started (sandbox=${x()})`);
5
+ `;return i===r?!1:(Se(n,i,c)||E.writeFileSync(n,i,{mode:c}),!0)}import p from"node:fs";import V from"node:path";var X="<!-- typhons-public-url -->",z="<!-- /typhons-public-url -->",Z="d.typhons.dev",Ee=[{dir:".claude",file:"CLAUDE.md"},{dir:".codex",file:"AGENTS.md"}];function ge(e,n=Z){let t=`https://${e}.${n}`;return[X,`This server is publicly reachable at ${t}:<PORT>`,"(where <PORT> is whatever local port your dev server binds to \u2014 e.g.",`${t}:3000 for a server bound to :3000). When you stand something up`,"or want the user to test, hand them that URL \u2014 they can't reach","localhost inside this sandbox, but they can reach the public URL.","","After making changes, link the user to the dev server when applicable so they can test.",z].join(`
6
+ `)}function _e(e,n){let t=new RegExp(`${X}[\\s\\S]*?${z}`,"m");if(t.test(e))return e.replace(t,n);let r=e?e.endsWith(`
7
+
8
+ `)?"":e.endsWith(`
9
+ `)?`
10
+ `:`
11
+
12
+ `:"";return e+r+n+`
13
+ `}function Te(){let e=[];try{p.existsSync("/root")&&e.push("/root")}catch{}try{for(let n of p.readdirSync("/home")){let t=`/home/${n}`;try{p.statSync(t).isDirectory()&&e.push(t)}catch{}}}catch{}return e}function Q(e,n=Z,t=r=>process.stderr.write(r+`
14
+ `)){let r=ge(e,n),c=typeof process.getuid=="function"?process.getuid():0;for(let o of Te()){let l=0,i=0;try{let d=p.statSync(o);l=d.uid,i=d.gid}catch{continue}if(!(c!==0&&l!==c))for(let{dir:d,file:_}of Ee){let f=V.join(o,d),h=V.join(f,_);try{let m=p.existsSync(f);m||p.mkdirSync(f,{recursive:!0});let L=p.existsSync(h)?p.readFileSync(h,"utf8"):"",U=_e(L,r);U!==L&&p.writeFileSync(h,U);try{m||p.chownSync(f,l,i)}catch{}try{p.chownSync(h,l,i)}catch{}}catch(m){try{t(`[agent-md] ${h}: ${m.message}`)}catch{}}}}}var at=ie(ae(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",be=process.env.RELAY_SCHEME||"wss",b=process.env.E2B_RUN_DIR||"/run/e2b",Re=`${b}/.RELAY_TOKEN`,ve=`${b}/.E2B_SANDBOX_ID`,q=`${b}/.SAVED_SANDBOX_ID`,Ie=`${b}/.DEVBOX_NAME`,we=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),Oe=3e3,ee=15e3,xe=6e4,te=parseInt(process.env.RELAY_CLIENT_VERIFY_REJECT_RETRY_MS||"10000",10),ne="@typhons/sandbox-tools",De=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",v=parseInt(process.env.KEEPALIVE_PORT||"49999");function le(){try{return a.readFileSync(Re,"utf8").trim()}catch{}return""}var He=`${b}/.TYPHONS_SHARED_MOUNT`;function k(){let e={};try{for(let n of a.readFileSync(He,"utf8").split(`
15
+ `)){let t=n.match(/^([A-Z_]+)=(.*)$/);t&&(e[t[1]]=t[2])}}catch{}return e}function I(){let e=process.env.TYPHONS_SHARE_CREDENTIALS??k().TYPHONS_SHARE_CREDENTIALS;if(e==="0")return!1;if(e==="1")return!0;try{let n=["/root"];try{n.push(...a.readdirSync("/home").map(t=>`/home/${t}`))}catch{}return n.some(t=>a.existsSync(`${t}/.config/typhons-share/rclone.conf`))}catch{return!1}}function P(){if(I())try{let e=N.join(ie(ae(import.meta.url)),"typhons-share-mount.js"),[n,t]=a.existsSync(e)?[process.execPath,[e]]:["/opt/sandbox/bin/typhons-share-mount",[]],r=se(n,t,{stdio:["ignore","inherit","inherit"],detached:!0,env:{...k(),...process.env}});r.on("error",c=>s(`typhons-share-mount spawn failed: ${c.message}`)),r.unref()}catch(e){s(`typhons-share-mount failed to start: ${e.message}`)}}var ue=[".claude/.credentials.json",".config/gh/hosts.yml",".codex/auth.json"],Ce=".typhons-shared-mount";function H(e,n){try{let t=a.realpathSync(N.join(e,Ce)),r=a.realpathSync(N.dirname(n));return r===t||r.startsWith(t+"/")}catch{return!1}}function de(){let e=["/root"];try{e.push(...a.readdirSync("/home").map(n=>`/home/${n}`))}catch{}return e.filter(n=>{try{return a.existsSync(`${n}/.config/typhons-share/rclone.conf`)}catch{return!1}})}var O=null;function fe(e){O||(s(`${e} replaced by a regular file (login?) \u2014 folding into the share`),O=setTimeout(()=>{O=null,P()},300))}var x=new Set;function ke(){if(I())for(let e of de())for(let n of ue){let t=`${e}/${n}`,r=N.dirname(t),c=N.basename(t);if(!(x.has(r)||!a.existsSync(r))&&!H(e,t))try{let o=a.watch(r,(l,i)=>{if(i===c)try{a.lstatSync(t).isFile()&&!H(e,t)&&fe(t)}catch{}});o.on("error",()=>{x.delete(r);try{o.close()}catch{}}),x.add(r)}catch{}}}function Pe(){if(ke(),!!I())try{for(let e of de())for(let n of ue)try{if(a.lstatSync(`${e}/${n}`).isFile()&&!H(e,`${e}/${n}`)){fe(`${e}/${n}`);return}}catch{}}catch{}}setInterval(Pe,1e4);function he(e=0){if((process.env.TYPHONS_SHARE_CREDENTIALS??k().TYPHONS_SHARE_CREDENTIALS)!=="0"){if(!I()||!le()){e<120?setTimeout(()=>he(e+1),500):s("share mount boot kick: gave up waiting for token/config");return}s("share mount boot kick: starting typhons-share-mount"),P()}}function B(){try{return a.readFileSync(ve,"utf8").trim()}catch{}return"unknown"}function s(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var re=process.env.AGENT_MD_HTTP_DOMAIN||"d.typhons.dev",g="unknown";function Be(){if(process.env.RELAY_URL)return process.env.RELAY_URL;g=B();try{let e=a.readFileSync(q,"utf8").trim();if(e&&e!==g){s(`Clone detected (${e} -> ${g}), interrupting agents...`);try{$("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{a.writeFileSync(q,g)}catch{}}}catch{}return`${be}://${Ne}/tunnel/${g}`}var S=null,D=null;function Le(){return j(F,{onConnect(n){s("Connected to session-proxy via IPC"),S=n,D=new R(t=>{u&&u.readyState===A.OPEN&&u.send(t)},t=>{u&&u.readyState===A.OPEN&&u.send(JSON.stringify(t))}),n.on("data",t=>D.feed(t))},onClose(){s("IPC connection to session-proxy lost, will retry..."),S=null,D=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&s(`IPC error: ${n.message}`)}})}var u=null,C=null,y=0,T=null;function oe(e=we){if(u){try{u.terminate()}catch{}u=null}T&&(clearTimeout(T),T=null),T=setTimeout(()=>{T=null,pe()},e)}function pe(){let e=++y,n=Be();s(`Connecting to ${n}... (gen=${e}, sandbox=${g})`);let t={},r=le();if(r&&(t.Authorization=`Bearer ${r}`),u){try{u.terminate()}catch{}u=null}let c=new A(n,{headers:t});u=c;let o=null,l=!0,i=setTimeout(()=>{if(e===y){s(`Handshake timeout after ${ee}ms \u2014 aborting and reconnecting`);try{c.terminate()}catch{}d()}},ee);function d(){i&&(clearTimeout(i),i=null),o&&(clearInterval(o),o=null),e===y&&oe()}function _(){l=!0,o=setInterval(()=>{if(e!==y){clearInterval(o);return}if(!l){s("No pong received \u2014 connection dead, reconnecting"),d();return}l=!1;try{c.ping()}catch{d()}},Oe)}c.on("open",()=>{if(e!==y){c.terminate();return}i&&(clearTimeout(i),i=null),s(`Connected${C?` (reconnect, port ${C})`:""} (gen=${e})`),_()}),c.on("pong",()=>{e===y&&(l=!0)}),c.on("message",(f,h)=>{if(e===y){if(!h){try{Ue(JSON.parse(f.toString()))}catch(m){s(`Bad control msg: ${m.message}`)}return}S&&!S.destroyed&&J(S,Buffer.from(f))}}),c.on("close",(f,h)=>{if(e!==y)return;let m=h?h.toString():"";if(f===4100&&(s(`Replaced by newer connection (code 4100, reason=${m}) \u2014 exiting`),process.exit(42)),f===4101){s(`Identity verification rejected (code 4101, reason=${m}) \u2014 retrying in ${te}ms (likely waiting for envd to update /run/e2b/.E2B_SANDBOX_ID)`),i&&(clearTimeout(i),i=null),o&&(clearInterval(o),o=null),e===y&&oe(te);return}s(`Data WS closed: code=${f}, reason=${m}`),d()}),c.on("error",f=>{e===y&&(s(`Data WS error: ${f.message} (code=${f.code||"none"})`),d())}),c.on("unexpected-response",(f,h)=>{e===y&&(s(`Data WS unexpected response: status=${h.statusCode}`),d())})}function Ue(e){if(e.type==="port"){C=e.port,s(`Assigned port ${e.port}`);return}if(e.type==="name"){if(s(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){let n=`${e.name}.local`;try{Ae("hostname",[n],{timeout:5e3,stdio:"ignore"}),a.writeFileSync("/etc/hostname",n+`
16
+ `)}catch{}try{G(n)&&s(`Updated /etc/hosts entry for ${n}`)}catch(t){s(`Failed to update /etc/hosts: ${t.message}`)}try{a.writeFileSync(Ie,e.name)}catch{}try{Q(e.name,re,s)}catch(t){s(`updateAgentMd failed: ${t.message}`)}try{let t=`${e.name}.${re}`,r=`https://${t}`;a.writeFileSync("/etc/profile.d/typhons.sh",`export TYPHONS_HOST=${JSON.stringify(t)}
17
+ export TYPHONS_URL=${JSON.stringify(r)}
18
+ `,{mode:420})}catch(t){s(`Failed to write /etc/profile.d/typhons.sh: ${t.message}`)}P()}return}S&&!S.destroyed&&K(S,e)}function ce(){try{let e=$(`npm view ${ne} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(a.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){s(`Update available: ${n} \u2192 ${e}, installing...`),$(`npm install -g --prefix /opt/sandbox ${ne}@latest`,{timeout:12e4,stdio:"inherit"});try{$('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=a.openSync("/tmp/claude-keepalive.log","a");se("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),a.closeSync(t)}catch{}s("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function Fe(){let e=$e.createServer((t,r)=>{if(r.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){r.end(JSON.stringify({ok:!0,sandboxId:B(),wsConnected:!!(u&&u.readyState===A.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{r.writeHead(400),r.end('{"error":"bad json"}');return}if(typeof o!="string"||!o.length||o.length>256){r.writeHead(400),r.end('{"error":"missing or invalid nonce"}');return}if(!u||u.readyState!==A.OPEN){r.writeHead(503),r.end('{"error":"WS not connected"}');return}try{u.send(JSON.stringify({type:"verify-ack",nonce:o}))}catch(l){r.writeHead(500),r.end(`{"error":"${l.message}"}`);return}r.writeHead(200),r.end('{"ok":true}')}),t.on("error",()=>{try{r.writeHead(400),r.end('{"error":"req error"}')}catch{}});return}r.writeHead(404),r.end('{"error":"not found"}')}),n=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!n){n=!0,s(`Port ${v} 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(v,"0.0.0.0")}catch{}},1500);return}s(`Keepalive HTTP server error: ${t.message}`)}),e.listen(v,"0.0.0.0",()=>{s(`Keepalive HTTP server on :${v}`)}),e}var lt=Le();Fe();pe();De||(setTimeout(ce,1e4),setInterval(ce,xe));s(`Relay client started (sandbox=${B()})`);he();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import r from"fs";import g from"os";import u from"path";import k from"crypto";import{execFileSync as _,spawn as A,spawnSync as h}from"child_process";var w=".typhons-shared-mount",v="/run/e2b/.RELAY_TOKEN",U="/run/e2b/.TYPHONS_SHARED_MOUNT",N=2e4;function H(e){if(process.env[e]!==void 0)return process.env[e];try{let t=r.readFileSync(U,"utf8").match(new RegExp(`^${e}=(.*)$`,"m"));if(t)return t[1]}catch{}}var I=()=>H("TYPHONS_SERVER_URL")||"https://www.typhons.dev",D=[{from:".claude/.credentials.json",to:"claude/credentials.json"},{from:".config/gh/hosts.yml",to:"gh/hosts.yml"},{from:".codex/auth.json",to:"codex/auth.json"}];function s(e){console.error(`[typhons-share ${new Date().toISOString().slice(11,19)}] ${e}`)}var L=Buffer.from([156,147,91,72,115,10,85,77,107,253,124,99,200,134,169,43,211,144,25,142,184,18,138,251,244,222,22,43,139,149,246,56]);function Y(e){let t=k.randomBytes(16),n=k.createCipheriv("aes-256-ctr",L,t),o=Buffer.concat([n.update(e,"utf8"),n.final()]);return Buffer.concat([t,o]).toString("base64url")}function F(e){return`# Managed by typhons-share-mount \u2014 do not edit.
3
+ [b2]
4
+ type = b2
5
+ account = ${e.key_id}
6
+ key = ${e.application_key}
7
+ hard_delete = true
8
+
9
+ [share]
10
+ type = crypt
11
+ remote = b2:${e.bucket}/${e.prefix}
12
+ password = ${Y(e.crypt_password)}
13
+ `}function C(e,t,n=384){r.mkdirSync(u.dirname(e),{recursive:!0,mode:448});let o=`${e}.tmp.${process.pid}`;r.writeFileSync(o,t,{mode:n}),r.renameSync(o,e)}function y(e){try{let t=r.readFileSync("/proc/mounts","utf8").split(`
14
+ `);for(let n of t){let o=n.split(/\s+/);if(o[1]===e)return{type:o[2],options:o[3]||""}}}catch{}return null}function T(e){return h("stat",["-f",e],{timeout:5e3,stdio:"ignore"}).status===0}function P(e){for(let[t,n]of[["fusermount3",["-uz",e]],["fusermount",["-uz",e]],["umount",["-l",e]]])try{return _(t,n,{stdio:"ignore",timeout:5e3}),!0}catch{}return!1}function j(e,t){try{return r.readFileSync(e).equals(r.readFileSync(t))}catch{return!1}}function O(e,t){if(r.copyFileSync(e,t),!j(e,t))throw new Error("readback mismatch after copy")}function R(e,t){let n=`${t}.tmp-link.${process.pid}`;try{r.unlinkSync(n)}catch{}return r.symlinkSync(e,n),n}function b(e,t,n=D){let o=[],a=t;try{a=r.realpathSync(t)}catch{}for(let{from:i,to:f}of n){let c=u.join(e,i),l=u.join(t,f),d="none";try{let m=null;try{m=r.realpathSync(u.dirname(c))}catch{}if(m&&(m===a||m.startsWith(a+u.sep))){o.push({from:i,action:"already-in-mount"});continue}r.mkdirSync(u.dirname(l),{recursive:!0,mode:448}),r.mkdirSync(u.dirname(c),{recursive:!0,mode:448});let p=null;try{p=r.lstatSync(c)}catch{}if(p?.isSymbolicLink())r.readlinkSync(c)!==l&&(r.renameSync(R(l,c),c),d="relinked");else if(p){let x=null;try{x=r.statSync(l)}catch{}x?j(c,l)?d="already-synced":p.mtimeMs>x.mtimeMs?(O(c,l),d="migrated-newer-local"):d="kept-newer-remote":(O(c,l),d="migrated");let M=R(l,c),$=`${c}.pre-share.bak`;r.existsSync($)&&($=`${c}.pre-share.${Date.now()}.bak`),r.renameSync(c,$),r.renameSync(M,c)}else r.symlinkSync(l,c),d="linked";d!=="none"&&d!=="already-synced"&&s(`${i}: ${d}`),o.push({from:i,action:d})}catch(m){try{r.unlinkSync(`${c}.tmp-link.${process.pid}`)}catch{}s(`${i}: migration failed, leaving local file in place: ${m.message}`),o.push({from:i,action:"failed",error:m.message})}}return o}async function K(){if(process.env.TYPHONS_SHARE_TOKEN_JSON){let n=JSON.parse(process.env.TYPHONS_SHARE_TOKEN_JSON);return s(n?`using pre-fetched credentials (prefix=${n.prefix})`:"running credential-less (healing with the existing config)"),n}let e;try{e=r.readFileSync(v,"utf8").trim()}catch{}if(!e)throw new Error(`no tunnel token at ${v}`);let t=await fetch(`${I()}/api/typhons-share/token`,{method:"POST",headers:{Authorization:`Bearer ${e}`},signal:AbortSignal.timeout(2e4)});if(!t.ok){let n=await t.text().catch(()=>"");throw new Error(`credential fetch failed: HTTP ${t.status} ${n.slice(0,200)}`)}return await t.json()}function q(e){let t=_("getent",["passwd",e],{timeout:5e3}).toString().trim().split(":");return{uid:Number(t[2]),gid:Number(t[3]),home:t[5]}}function B(){let e=[];for(let[o,a]of[["rclone","rclone"],["fusermount3","fuse3"]])h("sh",["-c",`command -v ${o}`],{stdio:"ignore"}).status===0||e.push(a);try{r.existsSync("/dev/fuse")||_("mknod",["/dev/fuse","c","10","229"],{timeout:5e3}),(r.statSync("/dev/fuse").mode&438)!==438&&r.chmodSync("/dev/fuse",438)}catch(o){s(`could not prepare /dev/fuse: ${o.message}`)}if(r.existsSync("/etc/ssl/certs/ca-certificates.crt")||e.push("ca-certificates"),!e.length)return;s(`installing missing packages: ${e.join(" ")}`);let t={...process.env,DEBIAN_FRONTEND:"noninteractive"};h("apt-get",["update","-qq"],{stdio:"ignore",timeout:12e4,env:t});let n=h("apt-get",["install","-y","-qq","--no-install-recommends",...e],{stdio:"ignore",timeout:18e4,env:t});n.status!==0&&s(`apt-get install failed (status ${n.status}) \u2014 mount will likely fail`)}function z(e){let t=y(e);if(!t)return;Number(t.options.match(/user_id=(\d+)/)?.[1]??0)===0&&(s(`unmounting legacy root-owned mount at ${e}`),P(e))}async function E(e,{home:t}={}){t=t||g.homedir();let n=u.join(t,w),o=u.join(t,".config","typhons-share","rclone.conf"),a=u.join(t,".cache","typhons-share");if(e)C(o,F(e));else if(!r.existsSync(o))return s("no credentials and no existing rclone config \u2014 nothing to do"),{mounted:!1,reason:"no-config"};if(r.mkdirSync(n,{recursive:!0,mode:493}),r.mkdirSync(a,{recursive:!0,mode:448}),y(n)&&T(n))return b(t,n),s(`mount healthy at ${n}`),{mounted:!0,reason:"already-healthy"};y(n)&&(s(`dead mount at ${n}; lazy-unmounting`),P(n));let i=["mount","share:",n,"--config",o,"--vfs-cache-mode","full","--vfs-cache-max-size","512M","--cache-dir",a,"--vfs-write-back","1s","--dir-cache-time","5s","--file-perms","0600","--dir-perms","0700","--low-level-retries","7","--contimeout","15s","--timeout","30s","--log-level","INFO","--log-file",u.join(a,"rclone.log")];s(`mounting: rclone ${i.join(" ")}`);let f=A("rclone",i,{stdio:"ignore",detached:!0});f.on("error",l=>s(`rclone spawn failed: ${l.message}`)),f.unref();let c=Date.now()+N;for(;Date.now()<c;){if(y(n)&&T(n))return b(t,n),s(`mount live at ${n}`),{mounted:!0,reason:"fresh-mount"};await new Promise(l=>setTimeout(l,100))}return s(`mount did not come up within ${N/1e3}s (see ${a}/rclone.log)`),{mounted:!1,reason:"mount-timeout"}}var S="/tmp/typhons-share-mount.lock";function J(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}function V(){for(let e=0;e<2;e++)try{let t=r.openSync(S,"wx");return r.writeSync(t,String(process.pid)),r.closeSync(t),!0}catch(t){if(t.code!=="EEXIST")return!0;let n=0;try{n=parseInt(r.readFileSync(S,"utf8"),10)}catch{}if(n&&J(n))return!1;try{r.unlinkSync(S)}catch{}}return!1}function W(){try{r.unlinkSync(S)}catch{}}async function G(){if(H("TYPHONS_SHARE_CREDENTIALS")==="0")return s("TYPHONS_SHARE_CREDENTIALS=0 \u2014 skipping"),{mounted:!1,reason:"opted-out"};if(g.userInfo().uid===0&&!V())return s("another typhons-share-mount is already running \u2014 skipping"),{mounted:!1,reason:"locked"};try{return await Q()}finally{g.userInfo().uid===0&&W()}}function X(){let e=["/root"];try{e.push(...r.readdirSync("/home").map(t=>`/home/${t}`))}catch{}for(let t of e)try{if(r.existsSync(u.join(t,".config","typhons-share","rclone.conf")))return t==="/root"?"root":u.basename(t)}catch{}return null}async function Q(){let e=null;try{e=await K()}catch(a){s(`credential fetch failed: ${a.message}`)}if(g.userInfo().uid!==0)return E(e);let n=process.env.TYPHONS_SHARE_MOUNT_USER||e?.remote_user||X()||(e?"user":null);if(!n)return{mounted:!1,reason:"fetch-failed"};if(n==="root")return E(e,{home:"/root"});let o;try{o=q(n)}catch{return s(`user "${n}" not found \u2014 falling back to root home mount`),E(e,{home:"/root"})}B(),z(u.join(o.home,w));for(let a of[".config",".cache",u.join(".config","typhons-share"),u.join(".cache","typhons-share"),w]){let i=u.join(o.home,a);try{r.mkdirSync(i,{recursive:!0}),r.statSync(i).uid===0&&r.chownSync(i,o.uid,o.gid)}catch(f){s(`could not prepare ${i}: ${f.message}`)}}return new Promise(a=>{let i=A(process.execPath,[process.argv[1]],{uid:o.uid,gid:o.gid,env:{PATH:process.env.PATH,HOME:o.home,USER:n,LOGNAME:n,TYPHONS_SHARE_TOKEN_JSON:JSON.stringify(e),TYPHONS_SHARE_MOUNT_USER:n},stdio:["ignore","inherit","inherit"]});i.on("error",f=>{s(`user-phase spawn failed: ${f.message}`),a({mounted:!1,reason:"spawn-failed"})}),i.on("exit",f=>a({mounted:f===0,reason:`user-phase-exit-${f}`}))})}import.meta.url===`file://${process.argv[1]}`&&G().then(e=>{process.exit(e.mounted?0:1)}).catch(e=>{s(`fatal: ${e.stack||e.message}`),process.exit(2)});export{D as CREDENTIAL_SYMLINKS,b as ensureSymlinks,y as mountEntry,Y as obscure,T as probeMount,F as renderRcloneConfig,G as runOnce,E as runUserPhase,C as writeFileAtomic};