@coboot/headless 0.2.2-rc.2 → 0.2.2-rc.4

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/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # Coboot Headless
2
2
 
3
- Node.js 20+ 的无 GUI 后台运行包。本包不携带浏览器二进制,也不会自动下载浏览器。
3
+ Node.js 18+ 的无 GUI 后台运行包。本包不携带浏览器二进制,也不会自动下载浏览器。
4
+ Headless 业务运行时要求 Node.js 20+;当入口检测到 Node.js 18/19 时,会通过 `npx` 临时下载并使用
5
+ 隔离的 Node.js 22 LTS,不修改系统 Node.js。Node.js 20+ 环境直接使用当前 Node,不产生额外下载。
4
6
  首次启动只需一次性设备 token:
5
7
 
6
8
  ```bash
7
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
9
+ npx -y @coboot/headless@0.2.2-rc.4 '<device-token>'
8
10
  ```
9
11
 
10
12
  命令会把运行时安装到 `/data/coboot/.siboot`,在客户端鉴权和运行时初始化完成后启动
@@ -12,14 +14,23 @@ detached daemon 并退出。不需要设置 `BACKEND_URL`、`CLIENT_UID`。终
12
14
  目标机安装 Chromium。
13
15
 
14
16
  ```bash
15
- npx -y @coboot/headless@0.2.2-rc.2 status
16
- npx -y @coboot/headless@0.2.2-rc.2 logs
17
- npx -y @coboot/headless@0.2.2-rc.2 restart
18
- npx -y @coboot/headless@0.2.2-rc.2 stop
17
+ npx -y @coboot/headless@0.2.2-rc.4 status
18
+ npx -y @coboot/headless@0.2.2-rc.4 logs
19
+ npx -y @coboot/headless@0.2.2-rc.4 restart
20
+ npx -y @coboot/headless@0.2.2-rc.4 stop
19
21
  ```
20
22
 
23
+ 更新到 npm `latest` 并重启:
24
+
25
+ ```bash
26
+ npx -y @coboot/headless@latest update
27
+ ```
28
+
29
+ `update` 会先完成 Node.js 版本、设备凭证和新包完整性预检,再停止旧进程并切换版本;新版本启动失败时
30
+ 会自动恢复并重启旧版本。`restart` 只重启已经安装的版本,不再隐式替换运行时。
31
+
21
32
  默认数据根目录可通过 `SIBOOT_DATA_ROOT` 修改。该设置不会写入配置,首次安装以及后续的
22
- `status`、`logs`、`restart`、`stop` 都必须传入同一个值,否则 CLI 会操作 edition 默认目录。
33
+ `status`、`logs`、`restart`、`update`、`stop` 都必须传入同一个值,否则 CLI 会操作 edition 默认目录。
23
34
 
24
35
 
25
36
  ## Coboot 浏览器
@@ -30,13 +41,13 @@ Coboot daemon 默认禁用 updater 和浏览器预热,第一次调用 `browser
30
41
 
31
42
  ```bash
32
43
  HEADLESS_BROWSER_EXECUTABLE=/absolute/path/to/chrome \
33
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
44
+ npx -y @coboot/headless@0.2.2-rc.4 '<device-token>'
34
45
  ```
35
46
 
36
47
  需要安装浏览器时,由用户执行:
37
48
 
38
49
  ```bash
39
- npx -y @coboot/headless@0.2.2-rc.2 install-browser
50
+ npx -y @coboot/headless@0.2.2-rc.4 install-browser
40
51
  ```
41
52
 
42
53
  该命令不需要设备 token,也不要求 root。它会先检查现有浏览器并执行启动验证;浏览器已经可用时
@@ -74,7 +85,7 @@ dnf/yum 先试仓库中的 `chromium`,x86_64 回退到官方 RPM 并关闭 wea
74
85
  已有设备凭证无效或需要切换设备身份时,显式重新注册:
75
86
 
76
87
  ```bash
77
- npx -y @coboot/headless@0.2.2-rc.2 --force-enroll '<device-token>'
88
+ npx -y @coboot/headless@0.2.2-rc.4 --force-enroll '<device-token>'
78
89
  ```
79
90
 
80
91
  命令会先停止 daemon 并临时备份旧凭证;新凭证和客户端均就绪后才完成替换,失败时恢复旧凭证。
@@ -111,7 +122,7 @@ HTTP 与 WebSocket 必须同域并与设备 token 匹配:
111
122
  HEADLESS_ALLOW_ENDPOINT_OVERRIDE=1 \
112
123
  BACKEND_URL=https://example.com \
113
124
  WS_URL=wss://example.com/wsschat \
114
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
125
+ npx -y @coboot/headless@0.2.2-rc.4 '<device-token>'
115
126
  ```
116
127
 
117
128
  WebSocket 覆盖变量名是 `WS_URL`,不是 `WSS_URL`。
@@ -125,7 +136,7 @@ endpoint 覆盖同样不会写入配置;后续执行 `restart` 时必须再次
125
136
  CU_EDITION=coboot \
126
137
  PKG_NAME=@coboot/headless \
127
138
  PKG_BIN_NAME=coboot-headless \
128
- PKG_VERSION=0.2.2-rc.2 \
139
+ PKG_VERSION=0.2.2-rc.4 \
129
140
  pnpm build:headless:npm
130
141
  ```
131
142
 
@@ -133,8 +144,8 @@ pnpm build:headless:npm
133
144
 
134
145
  ```bash
135
146
  npm pack --dry-run --json ./dist-headless
136
- tar -xOf dist-headless/coboot-headless-0.2.2-rc.2.tgz package/package.json
137
- sha256sum dist-headless/coboot-headless-0.2.2-rc.2.tgz
147
+ tar -xOf dist-headless/coboot-headless-0.2.2-rc.4.tgz package/package.json
148
+ sha256sum dist-headless/coboot-headless-0.2.2-rc.4.tgz
138
149
  ```
139
150
 
140
151
 
package/cli.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import{execFileSync as we,spawn as C}from"child_process";import{createHash as he}from"crypto";import a from"fs";import i from"path";import{fileURLToPath as Ee}from"url";var W=new Set(["help","install-browser","start","stop","restart","status","logs"]);function j(e="siboot-headless",n="/data/siboot",o=!1){let r=o?` ${e} install-browser
2
+ import{execFileSync as Me,spawn as q}from"child_process";import m from"fs";import i from"path";import{fileURLToPath as Ue}from"url";var oe=new Set(["help","install-browser","start","stop","restart","update","status","logs"]);function F(e="siboot-headless",r="/data/siboot",o=!1){let n=o?` ${e} install-browser
3
3
  `:"";console.log(`Usage:
4
4
  ${e} <device-token>
5
5
  ${e} --force-enroll <device-token>
6
- ${r} ${e} start|stop|restart|status|logs
6
+ ${n} ${e} start|stop|restart|update|status|logs
7
7
 
8
8
  Environment:
9
- SIBOOT_DATA_ROOT Persistent data root (default: ${n})`)}function A(e){let n=e[0]?.trim()||"";if(!n||n==="--help"||n==="-h"||n==="help")return{command:"help"};if(n==="--force-enroll"){let o=e[1]?.trim()||"";if(!o||e.length!==2)throw new Error("--force-enroll requires one device token");return{command:"install",token:o,forceEnroll:!0}}return W.has(n)?{command:n}:{command:"install",token:n}}import{execFileSync as k}from"child_process";import{createRequire as V}from"module";import p from"fs";import G from"os";import m from"path";var J=["/usr/local/bin/google-chrome","/usr/local/bin/chromium","/usr/local/bin/chromium-browser","/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/usr/bin/chromium","/usr/bin/chromium-browser"],Y="https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm",X="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb",K=[/error while loading shared libraries:\s*([^\s:]+)/gi,/([A-Za-z0-9_.+-]+\.so(?:\.[0-9A-Za-z.]+)?):\s*cannot open shared object file/gi,/Missing libraries:\s*([^]*?)(?:\n\s*\n|$)/gi],z=[/error while loading shared libraries:/i,/cannot open shared object file/i,/host system is missing dependencies/i,/missing libraries:/i];function x(e,n=process.env){let o=n.PLAYWRIGHT_BROWSERS_PATH?.trim();return o||m.join(e,"browsers")}function D(e){try{return p.accessSync(e,p.constants.X_OK),!0}catch{return!1}}function g(e){for(let n of(process.env.PATH||"").split(m.delimiter)){if(!n)continue;let o=m.join(n,e);if(D(o))return o}return null}function L(){let e=process.env.HEADLESS_BROWSER_EXECUTABLE?.trim();if(e&&D(e))return e;for(let n of J)if(D(n))return n;return g("google-chrome")||g("google-chrome-stable")||g("chromium")}function Z(e){return z.some(n=>n.test(e))}function q(e){let n=new Set;for(let o of K){o.lastIndex=0;for(let r of e.matchAll(o))for(let t of r[1].split(/[\s,]+/)){let s=t.trim().replace(/[.,:;]+$/,"");/\.so(?:\.[0-9A-Za-z.]+)?$/.test(s)&&n.add(s)}}return[...n]}function Q(e={arch:process.arch},n=g){let o=e.arch==="x64";if(n("apt-get"))return[{command:"apt-get",args:["update"],optional:!0},{attempts:[{command:"apt-get",args:["install","-y","--no-install-recommends","chromium"]},{command:"apt-get",args:["install","-y","--no-install-recommends","chromium-browser"]},...o?[{download:X,command:"apt-get",args:["install","-y","--no-install-recommends"]}]:[]]}];if(n("apk"))return[{attempts:[{command:"apk",args:["add","--no-cache","chromium"]},{command:"apk",args:["add","--no-cache","--repository=https://dl-cdn.alpinelinux.org/alpine/edge/community","chromium"]}]}];let r=n("dnf")?"dnf":n("yum")?"yum":"";if(r){let t=[{command:r,args:["install","-y","--setopt=install_weak_deps=False","chromium"]}];return o&&t.push({command:r,args:["install","-y","--setopt=install_weak_deps=False",Y]}),[{attempts:t}]}throw new Error("unsupported Linux package manager; install system Chrome/Chromium manually and set HEADLESS_BROWSER_EXECUTABLE")}async function ee(e){let n=p.mkdtempSync(m.join(G.tmpdir(),"coboot-browser-")),o=m.join(n,m.basename(new URL(e).pathname)||"browser-package");console.log(`Downloading: ${e}`);let r=await fetch(e);if(!r.ok)throw new Error(`download failed with HTTP ${r.status}: ${e}`);return p.writeFileSync(o,Buffer.from(await r.arrayBuffer())),{file:o,directory:n}}async function T(e){let n=e.download?await ee(e.download):null,o=n?[...e.args,n.file]:e.args;try{console.log(`Running: ${e.command} ${o.join(" ")}`),k(e.command,o,{stdio:"inherit",env:{...process.env,DEBIAN_FRONTEND:"noninteractive"}})}finally{n&&p.rmSync(n.directory,{recursive:!0,force:!0})}}function E(e){return e instanceof Error?e.message:String(e)}async function ne(){for(let e of Q()){if(!e.attempts){try{await T(e)}catch(r){if(!e.optional)throw r;console.warn(`Skipping optional step ${e.command}: ${E(r)}`)}continue}let n=[],o=!1;for(let r of e.attempts)try{await T(r),o=!0;break}catch(t){let s=r.download||r.args[r.args.length-1];n.push(`${s}: ${E(t)}`),console.warn(`Browser package candidate failed (${s}); trying the next one`)}if(!o)throw new Error(`no browser package could be installed:
10
- ${n.join(`
11
- `)}`)}}function I(e){let n=m.join(e,"browser-runtime","playwright-core"),o=V(m.join(e,"package.json"));return{driverRoot:n,driver:o(n)}}async function re(e){let{driverRoot:n,driver:o}=I(e),r=m.join(n,"cli.js"),t=process.env.PLAYWRIGHT_BROWSERS_PATH;try{p.mkdirSync(t,{recursive:!0,mode:448})}catch(s){throw new Error(`\u65E0\u6CD5\u521B\u5EFA\u6D4F\u89C8\u5668\u76EE\u5F55 ${t}: ${E(s)}
12
- \u8BF7\u786E\u8BA4\u5F53\u524D\u7528\u6237\u5BF9 SIBOOT_DATA_ROOT \u6709\u5199\u6743\u9650\uFF0C\u6216\u7528\u8BE5\u76EE\u5F55\u7684\u5C5E\u4E3B\u8D26\u53F7\u91CD\u65B0\u6267\u884C\u3002`)}return console.log(`Downloading Chromium into ${t}`),k(process.execPath,[r,"install","chromium","--no-shell"],{stdio:"inherit"}),o.chromium.executablePath()}async function oe(e,n){let{driver:o}=I(e),r=await o.chromium.launch({executablePath:n,headless:!0,args:["--no-sandbox","--disable-gpu","--disable-dev-shm-usage"]});try{let t=await r.newPage();if(await t.setContent("<title>Headless browser ready</title>"),await t.title()!=="Headless browser ready")throw new Error("browser smoke test returned wrong title");return r.version()}finally{await r.close()}}function te(e,n){let o=q(n),r=o.length?`\u7F3A\u5C11\u7CFB\u7EDF\u52A8\u6001\u5E93: ${o.join(", ")}`:"\u7F3A\u5C11\u7CFB\u7EDF\u4F9D\u8D56\u5E93";return new Error([`${e} \u65E0\u6CD5\u542F\u52A8\uFF0C${r}\u3002`,"\u8865\u9F50\u7CFB\u7EDF\u4F9D\u8D56\u9700\u8981 root \u6743\u9650\uFF0C\u8BF7\u7528 sudo \u91CD\u65B0\u6267\u884C\u540C\u4E00\u6761 install-browser \u547D\u4EE4\uFF0C\u6216\u8BA9\u7BA1\u7406\u5458\u5B89\u88C5\u4E0A\u8FF0\u5E93\u3002",`\u539F\u59CB\u9519\u8BEF: ${n}`].join(`
13
- `))}async function R(e,n){console.log(`Browser executable: ${n}`);try{let o=await oe(e,n);return console.log(`Browser smoke test passed: ${o}`),{executablePath:n,version:o}}catch(o){let r=E(o);throw Z(r)?te(n,r):o}}function ie(e){try{let n=m.join(e.run,"daemon.pid"),o=Number.parseInt(p.readFileSync(n,"utf8").trim(),10);return!Number.isInteger(o)||o<=0?null:p.statSync(`/proc/${o}`).uid}catch{return null}}function se(e,n){let o=ie(e),r=process.getuid?.();o===null||r===void 0||o===r||console.warn([`\u6CE8\u610F: daemon \u6B63\u4EE5 uid=${o} \u8FD0\u884C\uFF0C\u800C\u6D4F\u89C8\u5668\u88C5\u5728 uid=${r} \u540D\u4E0B\u7684 ${n}\u3002`,"daemon \u53EF\u80FD\u65E0\u6743\u8BFB\u53D6\u8BE5\u76EE\u5F55\u3002\u8BF7\u6539\u7528 daemon \u6240\u5C5E\u8D26\u53F7\u91CD\u65B0\u6267\u884C\u672C\u547D\u4EE4\uFF0C","\u6216\u4EE5 root \u6267\u884C\u8BA9\u5305\u7BA1\u7406\u5668\u628A\u6D4F\u89C8\u5668\u88C5\u5230\u7CFB\u7EDF\u8DEF\u5F84\u3002"].join(`
14
- `))}async function B(e,n){if(process.platform!=="linux")throw new Error("install-browser currently supports Linux only");let o=x(n.home);process.env.PLAYWRIGHT_BROWSERS_PATH=o;let r=L();if(r)try{return await R(e,r)}catch(c){console.warn(`Existing browser failed the smoke test: ${E(c)}`)}if(process.getuid?.()===0)try{await ne();let c=L();if(c)return await R(e,c);console.warn("\u5305\u7BA1\u7406\u5668\u6267\u884C\u5B8C\u6210\u4F46\u6CA1\u6709\u627E\u5230\u6D4F\u89C8\u5668\uFF0C\u56DE\u9000\u5230\u4E0B\u8F7D\u5B89\u88C5")}catch(c){console.warn(`\u7CFB\u7EDF\u5305\u7BA1\u7406\u5668\u5B89\u88C5\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u4E0B\u8F7D\u5B89\u88C5: ${E(c)}`)}let t=await re(e),s=await R(e,t);return console.log(`\u6D4F\u89C8\u5668\u5DF2\u88C5\u5165 ${o}\uFF0Cdaemon \u542F\u52A8\u65F6\u4F1A\u6307\u5411\u540C\u4E00\u76EE\u5F55\uFF0C\u65E0\u9700\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u3002`),se(n,o),r&&console.warn([`\u6CE8\u610F: ${r} \u4ECD\u7136\u5B58\u5728\u4E14\u4F1A\u88AB\u4F18\u5148\u4F7F\u7528\uFF0C\u4F46\u5B83\u65E0\u6CD5\u542F\u52A8\u3002`,`\u8BF7\u7528 HEADLESS_BROWSER_EXECUTABLE=${t} \u91CD\u65B0\u542F\u52A8 daemon\uFF0C\u6216\u4EE5 root \u4FEE\u590D\u8BE5\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002`].join(`
15
- `)),s}import w from"fs";var ae=e=>new Promise(n=>setTimeout(n,e));function N(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch{return!1}}function H(e){try{return JSON.parse(w.readFileSync(e,"utf8"))}catch{return null}}function ce(e){let n=H(e);return!!(n?.deviceId?.trim()&&n?.credentialId?.trim()&&n?.ownerUid?.trim()&&n?.deviceName?.trim()&&n?.deviceType>0&&n?.publicKey?.trim()&&n?.privateKeyPem?.trim())}function le(e){return[e.credential,e.authSession]}function de(e){for(let n of e.entries){try{w.unlinkSync(n.file)}catch{}n.existed&&w.renameSync(n.backup,n.file)}}function ue(e){let n=`${process.pid}-${Date.now()}`,o=le(e).map(t=>({file:t,backup:`${t}.force-enroll-${n}.bak`,existed:w.existsSync(t)})),r=[];try{for(let t of o)t.existed&&(w.renameSync(t.file,t.backup),r.push(t))}catch(t){for(let s of r.reverse())w.renameSync(s.backup,s.file);throw t}return{entries:o}}function me(e){for(let n of e.entries)n.existed&&w.unlinkSync(n.backup)}function fe(e){de(e)}async function pe(e,n){let o=Date.now()+n,r=0;for(;Date.now()<o;){let t=H(`${e.run}/status.json`);if(ce(e.credential)&&t?.client_ready===!0&&N(t.client_pid)){if(r||=Date.now(),Date.now()-r>=1e3)return}else r=0;let s=Number.parseInt(w.readFileSync(`${e.run}/daemon.pid`,"utf8"),10);if(t?.client_exit_code!=null&&!t?.client_pid)throw new Error(`client exited during force enrollment with code ${t.client_exit_code}`);if(s&&!N(s))throw new Error("daemon exited during force enrollment");await ae(100)}throw new Error(`force enrollment did not become ready within ${n}ms`)}async function P(e,n,o,r={}){let t=ue(e);try{let s=await n();return await pe(e,r.timeoutMs||15e3),me(t),s}catch(s){await o().catch(()=>{});try{fe(t)}catch(c){throw new AggregateError([s,c],"force enrollment and rollback failed")}throw s}}var F=Ee(import.meta.url),$={siboot:"/data/siboot",coboot:"/data/coboot"},Se=15e3,ge=["BACKEND_URL","WS_URL","SIBOOT_URL","MCP_CREDENTIAL_API_BASE_URL","SKILL_CREDENTIAL_API_BASE_URL"],be=["HEADLESS_BWRAP_EXECUTABLE","HEADLESS_BWRAP_LOADER","HEADLESS_BWRAP_LIBRARY_DIR"];function _e(e,n){let o=e||$[n],r=i.resolve(o);if(!i.isAbsolute(o)||r===i.parse(r).root)throw new Error(`SIBOOT_DATA_ROOT must be a non-root absolute path: ${e}`);return r}function ye(e=process.env.SIBOOT_DATA_ROOT,n="siboot"){if(!(n in $))throw new Error(`unsupported headless edition: ${n}`);let o=_e(e,n),r=i.join(o,".siboot");return{edition:n,root:o,home:r,releases:i.join(r,"releases"),current:i.join(r,"current"),scripts:i.join(r,"scripts"),run:i.join(r,"run"),logs:i.join(r,"logs"),workspace:i.join(o,"workspace"),credential:i.join(r,"headless-device-credential.json"),authSession:i.join(r,"headless-auth-session.json")}}function U(e){for(let n of[e.home,e.releases,e.scripts,e.run,e.workspace,i.join(e.logs,"daemon"),i.join(e.logs,"client"),i.join(e.logs,"update")])a.mkdirSync(n,{recursive:!0,mode:448})}function M(e){let n=JSON.parse(a.readFileSync(i.join(e,"package.json"),"utf8")),o=String(n.name||"").trim();if(!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(o))throw new Error("package name is missing or invalid");let r=String(n.version||"").trim();if(!r||!/^[0-9A-Za-z._-]+$/.test(r))throw new Error("package version is missing or invalid");let t=String(n.headlessEdition||"").trim();if(!(t in $))throw new Error("package headlessEdition must be siboot or coboot");let s=n.headlessLinuxSandbox??null;if(t==="siboot"&&s)throw new Error("Siboot package must not include Linux sandbox metadata");if(t==="coboot"&&(s?.backend!=="bubblewrap"||!Array.isArray(s.architectures)||s.architectures.length===0))throw new Error("Coboot package is missing bundled Linux sandbox metadata");let c=n.headlessBrowserRuntime??null;if(t==="siboot"&&c)throw new Error("Siboot package must not include guided browser runtime metadata");if(t==="coboot"&&(c?.driver!=="playwright-core"||!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(c.driverVersion||"")||c?.browser!=="system-or-guided-install"))throw new Error("Coboot package is missing valid guided browser runtime metadata");return{packageName:o,version:r,edition:t,linuxSandbox:s,browserRuntime:c}}function Ae(e,n){return`npx -y '${e}@${n}' install-browser`}function Re(e,n){let o=i.relative(e.home,n),r=`${e.current}.next-${process.pid}`;try{a.unlinkSync(r)}catch{}a.symlinkSync(o,r),a.renameSync(r,e.current)}function S(e,n,o){let r=`${n}.next-${process.pid}`;a.copyFileSync(e,r),a.chmodSync(r,o),a.renameSync(r,n)}function De(e,n,o){let r=`${e}.next-${process.pid}`;a.writeFileSync(r,n,{mode:o}),a.renameSync(r,e)}function xe(e){return he("sha256").update(a.readFileSync(e)).digest("hex")}function Oe(e,n){if(!a.existsSync(e)||xe(e)!==n)throw new Error(`bundled Linux sandbox file failed integrity check: ${i.basename(e)}`)}function $e(e,n,o,r){if(!o)return null;if(r.platform!=="linux")throw new Error(`Coboot Headless bundled sandbox supports Linux only: ${r.platform}`);if(!o.architectures.includes(r.arch))throw new Error(`Coboot Headless does not include Linux sandbox for arch: ${r.arch}`);let t=i.join(e,"native",`linux-${r.arch}`),s=i.join(t,"manifest.json");if(!a.existsSync(s))throw new Error(`package is missing Linux sandbox bundle for arch: ${r.arch}`);let c=JSON.parse(a.readFileSync(s,"utf8"));if(c.backend!==o.backend||c.backendVersion!==o.backendVersion||c.arch!==r.arch)throw new Error("bundled Linux sandbox manifest does not match package metadata");let l={bwrap:i.join(t,"bwrap"),loader:i.join(t,"ld-musl"),libcap:i.join(t,"lib","libcap.so.2")};for(let[u,f]of Object.entries(l))Oe(f,c.files?.[u]);let d=i.join(n,"native");return a.mkdirSync(i.join(d,"lib"),{recursive:!0,mode:448}),S(l.bwrap,i.join(d,"bwrap"),320),S(l.loader,i.join(d,"ld-musl"),320),S(l.libcap,i.join(d,"lib","libcap.so.2"),256),S(s,i.join(d,"manifest.json"),256),{backend:c.backend,version:c.backendVersion,arch:c.arch,bundled:!0}}function ve(e,n,o){let r=i.join(e,"browser-runtime","playwright-core"),t=i.join(r,"package.json");if(!a.existsSync(t))throw new Error("package is missing bundled playwright-core");let s=JSON.parse(a.readFileSync(t,"utf8"));if(s.name!==o.driver||s.version!==o.driverVersion)throw new Error("bundled playwright-core does not match package metadata");let c=i.join(n,"node_modules","playwright-core"),l=`${c}.next-${process.pid}`;return a.rmSync(l,{recursive:!0,force:!0}),a.mkdirSync(i.dirname(c),{recursive:!0,mode:448}),a.cpSync(r,l,{recursive:!0}),a.rmSync(c,{recursive:!0,force:!0}),a.renameSync(l,c),o}function je(e,n,o={platform:process.platform,arch:process.arch}){U(n);let{version:r,edition:t,linuxSandbox:s,browserRuntime:c}=M(e);if(t!==n.edition)throw new Error(`package edition ${t} does not match runtime layout ${n.edition}`);let l=i.join(n.releases,r);a.mkdirSync(l,{recursive:!0,mode:448});let d=i.join(e,"entry.mjs"),u=i.join(e,"daemon.mjs");if(!a.existsSync(d)||!a.existsSync(u))throw new Error("package is missing entry.mjs or daemon.mjs");S(d,i.join(l,"entry.mjs"),320),S(u,i.join(n.scripts,"daemon.mjs"),320);let f=$e(e,l,s,o),y=c?ve(e,l,c):"disabled";return De(i.join(l,"manifest.json"),`${JSON.stringify({type:"headless-npm-daemon",version:r,edition:t,browserRuntime:y,linuxSandbox:f},null,2)}
16
- `,256),Re(n,l),{version:r,releaseDir:l}}function _(e){try{let n=Number.parseInt(a.readFileSync(e,"utf8").trim(),10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function h(e,n=process.platform==="linux"?"/proc":""){if(!e)return!1;try{process.kill(e,0)}catch{return!1}if(!n)return!0;try{let o=a.readFileSync(i.join(n,String(e),"stat"),"utf8"),r=o.lastIndexOf(") ")+2;return r<2||o[r]!=="Z"}catch{return!0}}function Le(e,n){let o="";try{o=a.readFileSync(`/proc/${e}/cmdline`,"utf8").replaceAll("\0"," ").trim()}catch{}if(!o)try{o=we("ps",["-ww","-p",String(e),"-o","command="],{encoding:"utf8"}).trim()}catch{return!1}return[n,O(n)].some(r=>o.includes(r))}var v=e=>new Promise(n=>setTimeout(n,e));async function b(e){let n=i.join(e.run,"daemon.pid"),o=_(n);if(!h(o)){try{a.unlinkSync(n)}catch{}return!1}let r=i.join(e.scripts,"daemon.mjs");if(!Le(o,r))throw new Error(`refusing to stop unrelated process from stale daemon.pid: ${o}`);process.kill(o,"SIGTERM");for(let t=0;t<50&&h(o);t+=1)await v(100);if(h(o))throw new Error(`daemon did not stop within 5 seconds: ${o}`);return!0}function Te(e,n,o=process.env){let r={...o,CU_EDITION:e.edition,NO_ELECTRON:"1",CLIENT_TYPE:"anydev",SIBOOT_BASE_DIR:e.home,SIBOOT_DATA_ROOT:e.root,HEADLESS_DATA_DIR:e.root,SIBOOT_DATA_DIR:e.root,HEADLESS_DEVICE_CREDENTIAL_PATH:e.credential,HEADLESS_AUTH_SESSION_PATH:e.authSession,HEADLESS_DEVICE_REQUIRE_AUTH:"1",HEADLESS_PREWARM_DEFAULT_SESSION:"0",WORKSPACE:e.workspace,ANYDEV_UPDATE_ENABLED:"0"};n?r.HEADLESS_DEVICE_TOKEN=n:delete r.HEADLESS_DEVICE_TOKEN,delete r.CLIENT_UID,delete r.HEADLESS_DEVICE_FORCE_ENROLL;for(let t of be)delete r[t];if(e.edition==="coboot"){let t=i.join(e.current,"native");r.HEADLESS_BWRAP_EXECUTABLE=i.join(t,"bwrap"),r.HEADLESS_BWRAP_LOADER=i.join(t,"ld-musl"),r.HEADLESS_BWRAP_LIBRARY_DIR=i.join(t,"lib"),r.HEADLESS_BROWSER_RUNTIME="system-or-guided-install",r.PLAYWRIGHT_BROWSERS_PATH=x(e.home,o)}else delete r.HEADLESS_BROWSER_RUNTIME,delete r.HEADLESS_BROWSER_INSTALL_COMMAND;if(r.HEADLESS_ALLOW_ENDPOINT_OVERRIDE!=="1")for(let t of ge)delete r[t];return r}async function ke(e,n=Se){let o=Date.now()+n;for(;Date.now()<o;){let r=_(i.join(e.run,"daemon.pid"));if(!h(r))throw new Error("daemon exited during client startup");let t=null;try{t=JSON.parse(a.readFileSync(i.join(e.run,"status.json"),"utf8"))}catch{}if(t?.client_ready===!0&&h(t.client_pid))return;if(t?.client_exit_code!=null&&!t?.client_pid)throw new Error(`client exited during startup with code ${t.client_exit_code}`);await v(100)}throw new Error(`client did not become ready within ${n}ms`)}async function Ie(e,n="",o=process.env){U(e);let r=e.edition==="coboot"?"coboot-headless":"siboot-headless";if(!a.existsSync(e.credential)&&!n)throw new Error(`device credential is missing; run ${r} <device-token> first`);let t=i.join(e.scripts,"daemon.mjs");if(!a.existsSync(i.join(e.current,"entry.mjs"))||!a.existsSync(t))throw new Error(`runtime is not installed; run ${r} <device-token> first`);let s=a.existsSync(e.credential)?"":n,c=i.join(e.logs,"daemon",`${new Date().toISOString().slice(0,10)}.log`),l=a.openSync(c,"a",384);C(process.execPath,[t],{detached:!0,env:Te(e,s,o),stdio:["ignore",l,l]}).unref(),a.closeSync(l),await v(500);let u=_(i.join(e.run,"daemon.pid"));if(!h(u))throw new Error(`daemon failed to start; check ${c}`);return await ke(e),{pid:u,logFile:c}}function Be(e){let n=_(i.join(e.run,"daemon.pid")),o=null;try{o=JSON.parse(a.readFileSync(i.join(e.run,"status.json"),"utf8"))}catch{}console.log(JSON.stringify({running:h(n),pid:n,current:a.existsSync(e.current),status:o},null,2))}async function Ne(e){let n=i.join(e.logs,"daemon",`${new Date().toISOString().slice(0,10)}.log`);if(!a.existsSync(n))throw new Error(`log file not found: ${n}`);let o=C("tail",["-n","100","-f",n],{stdio:"inherit"});await new Promise((r,t)=>{o.once("error",t),o.once("exit",s=>s===0?r():t(new Error(`tail exited ${s}`)))})}async function He(){let e=A(process.argv.slice(2)),n=i.basename(process.argv[1]||"siboot-headless"),o=i.dirname(F),r=M(o),t=ye(process.env.SIBOOT_DATA_ROOT,r.edition);if(e.command==="help")return j(n,t.root,!!r.browserRuntime);if(e.command==="install-browser"){if(!r.browserRuntime)throw new Error("install-browser is available in the Coboot headless npm package only");return B(o,t)}let s=r.edition==="coboot"?"Coboot":"Siboot";if(e.command==="status")return Be(t);if(e.command==="logs")return Ne(t);if(e.command==="stop"){let u=await b(t);console.log(u?`${s} Headless stopped`:`${s} Headless is not running`);return}await b(t);let c=async()=>{let u=je(o,t);try{let f={...process.env};r.browserRuntime&&(f.HEADLESS_BROWSER_INSTALL_COMMAND=Ae(r.packageName,r.version));let y=await Ie(t,e.token||"",f);return{installed:u,started:y}}catch(f){throw await b(t).catch(()=>{}),f}},{installed:l,started:d}=e.forceEnroll?await P(t,c,()=>b(t)):await c();console.log(`${s} Headless ${l.version} started in background (pid=${d.pid})`),e.forceEnroll&&console.log("Device credential replaced by force enrollment"),console.log(`Logs: ${d.logFile}`)}function O(e){try{return a.realpathSync(e)}catch{return i.resolve(e)}}process.argv[1]&&O(process.argv[1])===O(F)&&He().catch(e=>{let n=i.basename(process.argv[1]||"headless-client");console.error(`${n}: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Te as createDaemonEnv,je as installHeadlessRuntime,h as isRunning,A as parseHeadlessCliArgs,M as readPackageMetadata,ye as resolveHeadlessLayout,Ie as startHeadlessRuntime,b as stopHeadlessRuntime};
9
+ SIBOOT_DATA_ROOT Persistent data root (default: ${r})`)}function I(e){let r=e[0]?.trim()||"";if(!r||r==="--help"||r==="-h"||r==="help")return{command:"help"};if(r==="--force-enroll"){let o=e[1]?.trim()||"";if(!o||e.length!==2)throw new Error("--force-enroll requires one device token");return{command:"install",token:o,forceEnroll:!0}}return oe.has(r)?{command:r}:{command:"install",token:r}}import{execFileSync as W}from"child_process";import{createRequire as te}from"module";import p from"fs";import se from"os";import f from"path";var ie=["/usr/local/bin/google-chrome","/usr/local/bin/chromium","/usr/local/bin/chromium-browser","/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/usr/bin/chromium","/usr/bin/chromium-browser"],ae="https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm",ce="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb",le=[/error while loading shared libraries:\s*([^\s:]+)/gi,/([A-Za-z0-9_.+-]+\.so(?:\.[0-9A-Za-z.]+)?):\s*cannot open shared object file/gi,/Missing libraries:\s*([^]*?)(?:\n\s*\n|$)/gi],de=[/error while loading shared libraries:/i,/cannot open shared object file/i,/host system is missing dependencies/i,/missing libraries:/i];function B(e,r=process.env){let o=r.PLAYWRIGHT_BROWSERS_PATH?.trim();return o||f.join(e,"browsers")}function H(e){try{return p.accessSync(e,p.constants.X_OK),!0}catch{return!1}}function $(e){for(let r of(process.env.PATH||"").split(f.delimiter)){if(!r)continue;let o=f.join(r,e);if(H(o))return o}return null}function M(){let e=process.env.HEADLESS_BROWSER_EXECUTABLE?.trim();if(e&&H(e))return e;for(let r of ie)if(H(r))return r;return $("google-chrome")||$("google-chrome-stable")||$("chromium")}function ue(e){return de.some(r=>r.test(e))}function me(e){let r=new Set;for(let o of le){o.lastIndex=0;for(let n of e.matchAll(o))for(let t of n[1].split(/[\s,]+/)){let s=t.trim().replace(/[.,:;]+$/,"");/\.so(?:\.[0-9A-Za-z.]+)?$/.test(s)&&r.add(s)}}return[...r]}function fe(e={arch:process.arch},r=$){let o=e.arch==="x64";if(r("apt-get"))return[{command:"apt-get",args:["update"],optional:!0},{attempts:[{command:"apt-get",args:["install","-y","--no-install-recommends","chromium"]},{command:"apt-get",args:["install","-y","--no-install-recommends","chromium-browser"]},...o?[{download:ce,command:"apt-get",args:["install","-y","--no-install-recommends"]}]:[]]}];if(r("apk"))return[{attempts:[{command:"apk",args:["add","--no-cache","chromium"]},{command:"apk",args:["add","--no-cache","--repository=https://dl-cdn.alpinelinux.org/alpine/edge/community","chromium"]}]}];let n=r("dnf")?"dnf":r("yum")?"yum":"";if(n){let t=[{command:n,args:["install","-y","--setopt=install_weak_deps=False","chromium"]}];return o&&t.push({command:n,args:["install","-y","--setopt=install_weak_deps=False",ae]}),[{attempts:t}]}throw new Error("unsupported Linux package manager; install system Chrome/Chromium manually and set HEADLESS_BROWSER_EXECUTABLE")}async function pe(e){let r=p.mkdtempSync(f.join(se.tmpdir(),"coboot-browser-")),o=f.join(r,f.basename(new URL(e).pathname)||"browser-package");console.log(`Downloading: ${e}`);let n=await fetch(e);if(!n.ok)throw new Error(`download failed with HTTP ${n.status}: ${e}`);return p.writeFileSync(o,Buffer.from(await n.arrayBuffer())),{file:o,directory:r}}async function U(e){let r=e.download?await pe(e.download):null,o=r?[...e.args,r.file]:e.args;try{console.log(`Running: ${e.command} ${o.join(" ")}`),W(e.command,o,{stdio:"inherit",env:{...process.env,DEBIAN_FRONTEND:"noninteractive"}})}finally{r&&p.rmSync(r.directory,{recursive:!0,force:!0})}}function _(e){return e instanceof Error?e.message:String(e)}async function we(){for(let e of fe()){if(!e.attempts){try{await U(e)}catch(n){if(!e.optional)throw n;console.warn(`Skipping optional step ${e.command}: ${_(n)}`)}continue}let r=[],o=!1;for(let n of e.attempts)try{await U(n),o=!0;break}catch(t){let s=n.download||n.args[n.args.length-1];r.push(`${s}: ${_(t)}`),console.warn(`Browser package candidate failed (${s}); trying the next one`)}if(!o)throw new Error(`no browser package could be installed:
10
+ ${r.join(`
11
+ `)}`)}}function V(e){let r=f.join(e,"browser-runtime","playwright-core"),o=te(f.join(e,"package.json"));return{driverRoot:r,driver:o(r)}}async function he(e){let{driverRoot:r,driver:o}=V(e),n=f.join(r,"cli.js"),t=process.env.PLAYWRIGHT_BROWSERS_PATH;try{p.mkdirSync(t,{recursive:!0,mode:448})}catch(s){throw new Error(`\u65E0\u6CD5\u521B\u5EFA\u6D4F\u89C8\u5668\u76EE\u5F55 ${t}: ${_(s)}
12
+ \u8BF7\u786E\u8BA4\u5F53\u524D\u7528\u6237\u5BF9 SIBOOT_DATA_ROOT \u6709\u5199\u6743\u9650\uFF0C\u6216\u7528\u8BE5\u76EE\u5F55\u7684\u5C5E\u4E3B\u8D26\u53F7\u91CD\u65B0\u6267\u884C\u3002`)}return console.log(`Downloading Chromium into ${t}`),W(process.execPath,[n,"install","chromium","--no-shell"],{stdio:"inherit"}),o.chromium.executablePath()}async function Ee(e,r){let{driver:o}=V(e),n=await o.chromium.launch({executablePath:r,headless:!0,args:["--no-sandbox","--disable-gpu","--disable-dev-shm-usage"]});try{let t=await n.newPage();if(await t.setContent("<title>Headless browser ready</title>"),await t.title()!=="Headless browser ready")throw new Error("browser smoke test returned wrong title");return n.version()}finally{await n.close()}}function Se(e,r){let o=me(r),n=o.length?`\u7F3A\u5C11\u7CFB\u7EDF\u52A8\u6001\u5E93: ${o.join(", ")}`:"\u7F3A\u5C11\u7CFB\u7EDF\u4F9D\u8D56\u5E93";return new Error([`${e} \u65E0\u6CD5\u542F\u52A8\uFF0C${n}\u3002`,"\u8865\u9F50\u7CFB\u7EDF\u4F9D\u8D56\u9700\u8981 root \u6743\u9650\uFF0C\u8BF7\u7528 sudo \u91CD\u65B0\u6267\u884C\u540C\u4E00\u6761 install-browser \u547D\u4EE4\uFF0C\u6216\u8BA9\u7BA1\u7406\u5458\u5B89\u88C5\u4E0A\u8FF0\u5E93\u3002",`\u539F\u59CB\u9519\u8BEF: ${r}`].join(`
13
+ `))}async function N(e,r){console.log(`Browser executable: ${r}`);try{let o=await Ee(e,r);return console.log(`Browser smoke test passed: ${o}`),{executablePath:r,version:o}}catch(o){let n=_(o);throw ue(n)?Se(r,n):o}}function ge(e){try{let r=f.join(e.run,"daemon.pid"),o=Number.parseInt(p.readFileSync(r,"utf8").trim(),10);return!Number.isInteger(o)||o<=0?null:p.statSync(`/proc/${o}`).uid}catch{return null}}function be(e,r){let o=ge(e),n=process.getuid?.();o===null||n===void 0||o===n||console.warn([`\u6CE8\u610F: daemon \u6B63\u4EE5 uid=${o} \u8FD0\u884C\uFF0C\u800C\u6D4F\u89C8\u5668\u88C5\u5728 uid=${n} \u540D\u4E0B\u7684 ${r}\u3002`,"daemon \u53EF\u80FD\u65E0\u6743\u8BFB\u53D6\u8BE5\u76EE\u5F55\u3002\u8BF7\u6539\u7528 daemon \u6240\u5C5E\u8D26\u53F7\u91CD\u65B0\u6267\u884C\u672C\u547D\u4EE4\uFF0C","\u6216\u4EE5 root \u6267\u884C\u8BA9\u5305\u7BA1\u7406\u5668\u628A\u6D4F\u89C8\u5668\u88C5\u5230\u7CFB\u7EDF\u8DEF\u5F84\u3002"].join(`
14
+ `))}async function J(e,r){if(process.platform!=="linux")throw new Error("install-browser currently supports Linux only");let o=B(r.home);process.env.PLAYWRIGHT_BROWSERS_PATH=o;let n=M();if(n)try{return await N(e,n)}catch(a){console.warn(`Existing browser failed the smoke test: ${_(a)}`)}if(process.getuid?.()===0)try{await we();let a=M();if(a)return await N(e,a);console.warn("\u5305\u7BA1\u7406\u5668\u6267\u884C\u5B8C\u6210\u4F46\u6CA1\u6709\u627E\u5230\u6D4F\u89C8\u5668\uFF0C\u56DE\u9000\u5230\u4E0B\u8F7D\u5B89\u88C5")}catch(a){console.warn(`\u7CFB\u7EDF\u5305\u7BA1\u7406\u5668\u5B89\u88C5\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u4E0B\u8F7D\u5B89\u88C5: ${_(a)}`)}let t=await he(e),s=await N(e,t);return console.log(`\u6D4F\u89C8\u5668\u5DF2\u88C5\u5165 ${o}\uFF0Cdaemon \u542F\u52A8\u65F6\u4F1A\u6307\u5411\u540C\u4E00\u76EE\u5F55\uFF0C\u65E0\u9700\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u3002`),be(r,o),n&&console.warn([`\u6CE8\u610F: ${n} \u4ECD\u7136\u5B58\u5728\u4E14\u4F1A\u88AB\u4F18\u5148\u4F7F\u7528\uFF0C\u4F46\u5B83\u65E0\u6CD5\u542F\u52A8\u3002`,`\u8BF7\u7528 HEADLESS_BROWSER_EXECUTABLE=${t} \u91CD\u65B0\u542F\u52A8 daemon\uFF0C\u6216\u4EE5 root \u4FEE\u590D\u8BE5\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002`].join(`
15
+ `)),s}import w from"fs";var _e=e=>new Promise(r=>setTimeout(r,e));function G(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch{return!1}}function X(e){try{return JSON.parse(w.readFileSync(e,"utf8"))}catch{return null}}function ye(e){let r=X(e);return!!(r?.deviceId?.trim()&&r?.credentialId?.trim()&&r?.ownerUid?.trim()&&r?.deviceName?.trim()&&r?.deviceType>0&&r?.publicKey?.trim()&&r?.privateKeyPem?.trim())}function ve(e){return[e.credential,e.authSession]}function Re(e){for(let r of e.entries){try{w.unlinkSync(r.file)}catch{}r.existed&&w.renameSync(r.backup,r.file)}}function Ae(e){let r=`${process.pid}-${Date.now()}`,o=ve(e).map(t=>({file:t,backup:`${t}.force-enroll-${r}.bak`,existed:w.existsSync(t)})),n=[];try{for(let t of o)t.existed&&(w.renameSync(t.file,t.backup),n.push(t))}catch(t){for(let s of n.reverse())w.renameSync(s.backup,s.file);throw t}return{entries:o}}function $e(e){for(let r of e.entries)r.existed&&w.unlinkSync(r.backup)}function De(e){Re(e)}async function je(e,r){let o=Date.now()+r,n=0;for(;Date.now()<o;){let t=X(`${e.run}/status.json`);if(ye(e.credential)&&t?.client_ready===!0&&G(t.client_pid)){if(n||=Date.now(),Date.now()-n>=1e3)return}else n=0;let s=Number.parseInt(w.readFileSync(`${e.run}/daemon.pid`,"utf8"),10);if(t?.client_exit_code!=null&&!t?.client_pid)throw new Error(`client exited during force enrollment with code ${t.client_exit_code}`);if(s&&!G(s))throw new Error("daemon exited during force enrollment");await _e(100)}throw new Error(`force enrollment did not become ready within ${r}ms`)}async function Y(e,r,o,n={}){let t=Ae(e);try{let s=await r();return await je(e,n.timeoutMs||15e3),$e(t),s}catch(s){await o().catch(()=>{});try{De(t)}catch(a){throw new AggregateError([s,a],"force enrollment and rollback failed")}throw s}}import{createHash as xe}from"crypto";import u from"fs";import d from"path";var Oe=new Set(["siboot","coboot"]);function P(e){for(let r of[e.home,e.releases,e.scripts,e.run,e.workspace,d.join(e.logs,"daemon"),d.join(e.logs,"client"),d.join(e.logs,"update")])u.mkdirSync(r,{recursive:!0,mode:448})}function D(e){let r=JSON.parse(u.readFileSync(d.join(e,"package.json"),"utf8")),o=String(r.name||"").trim();if(!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(o))throw new Error("package name is missing or invalid");let n=String(r.version||"").trim();if(!n||!/^[0-9A-Za-z._-]+$/.test(n))throw new Error("package version is missing or invalid");let t=String(r.headlessEdition||"").trim();if(!Oe.has(t))throw new Error("package headlessEdition must be siboot or coboot");let s=r.headlessLinuxSandbox??null;ke(t,s);let a=r.headlessBrowserRuntime??null;return Le(t,a),{packageName:o,version:n,edition:t,linuxSandbox:s,browserRuntime:a}}function ke(e,r){if(e==="siboot"&&r)throw new Error("Siboot package must not include Linux sandbox metadata");if(e==="coboot"&&(r?.backend!=="bubblewrap"||!Array.isArray(r.architectures)||r.architectures.length===0))throw new Error("Coboot package is missing bundled Linux sandbox metadata")}function Le(e,r){if(e==="siboot"&&r)throw new Error("Siboot package must not include guided browser runtime metadata");if(e==="coboot"&&(r?.driver!=="playwright-core"||!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(r.driverVersion||"")||r?.browser!=="system-or-guided-install"))throw new Error("Coboot package is missing valid guided browser runtime metadata")}function Te(e,r){let o=d.relative(e.home,r),n=`${e.current}.next-${process.pid}`;u.rmSync(n,{force:!0}),u.symlinkSync(o,n),u.renameSync(n,e.current)}function j(e){try{let r=u.readlinkSync(e.current);return d.resolve(d.dirname(e.current),r)}catch{try{return u.realpathSync(e.current)}catch{return null}}}function g(e,r,o){let n=`${r}.next-${process.pid}`;u.copyFileSync(e,n),u.chmodSync(n,o),u.renameSync(n,r)}function Ie(e,r,o){let n=`${e}.next-${process.pid}`;u.writeFileSync(n,r,{mode:o}),u.renameSync(n,e)}function Ne(e){return xe("sha256").update(u.readFileSync(e)).digest("hex")}function He(e,r){if(!u.existsSync(e)||Ne(e)!==r)throw new Error(`bundled Linux sandbox file failed integrity check: ${d.basename(e)}`)}function Be(e,r,o,n){if(!o)return null;if(n.platform!=="linux")throw new Error(`Coboot Headless bundled sandbox supports Linux only: ${n.platform}`);if(!o.architectures.includes(n.arch))throw new Error(`Coboot Headless does not include Linux sandbox for arch: ${n.arch}`);let t=d.join(e,"native",`linux-${n.arch}`),s=d.join(t,"manifest.json");if(!u.existsSync(s))throw new Error(`package is missing Linux sandbox bundle for arch: ${n.arch}`);let a=JSON.parse(u.readFileSync(s,"utf8"));if(a.backend!==o.backend||a.backendVersion!==o.backendVersion||a.arch!==n.arch)throw new Error("bundled Linux sandbox manifest does not match package metadata");let l={bwrap:d.join(t,"bwrap"),loader:d.join(t,"ld-musl"),libcap:d.join(t,"lib","libcap.so.2")};for(let[E,S]of Object.entries(l))He(S,a.files?.[E]);let c=d.join(r,"native");return u.mkdirSync(d.join(c,"lib"),{recursive:!0,mode:448}),g(l.bwrap,d.join(c,"bwrap"),320),g(l.loader,d.join(c,"ld-musl"),320),g(l.libcap,d.join(c,"lib","libcap.so.2"),256),g(s,d.join(c,"manifest.json"),256),{backend:a.backend,version:a.backendVersion,arch:a.arch,bundled:!0}}function Pe(e,r,o){let n=d.join(e,"browser-runtime","playwright-core"),t=d.join(n,"package.json");if(!u.existsSync(t))throw new Error("package is missing bundled playwright-core");let s=JSON.parse(u.readFileSync(t,"utf8"));if(s.name!==o.driver||s.version!==o.driverVersion)throw new Error("bundled playwright-core does not match package metadata");let a=d.join(r,"node_modules","playwright-core"),l=`${a}.next-${process.pid}`;return u.rmSync(l,{recursive:!0,force:!0}),u.mkdirSync(d.dirname(a),{recursive:!0,mode:448}),u.cpSync(n,l,{recursive:!0}),u.rmSync(a,{recursive:!0,force:!0}),u.renameSync(l,a),o}function x(e,r,o={platform:process.platform,arch:process.arch}){P(r);let{version:n,edition:t,linuxSandbox:s,browserRuntime:a}=D(e);if(t!==r.edition)throw new Error(`package edition ${t} does not match runtime layout ${r.edition}`);let l=d.join(r.releases,n);if(j(r)===l)return{version:n,releaseDir:l,reused:!0};let c=d.join(r.releases,`.staging-${n}-${process.pid}`);u.rmSync(c,{recursive:!0,force:!0}),u.mkdirSync(c,{recursive:!0,mode:448});try{Ce(e,c,{version:n,edition:t,linuxSandbox:s,browserRuntime:a},o),u.rmSync(l,{recursive:!0,force:!0}),u.renameSync(c,l)}catch(E){throw u.rmSync(c,{recursive:!0,force:!0}),E}return{version:n,releaseDir:l,reused:!1}}function Ce(e,r,o,n){let t=d.join(e,"entry.mjs"),s=d.join(e,"daemon.mjs");if(!u.existsSync(t)||!u.existsSync(s))throw new Error("package is missing entry.mjs or daemon.mjs");g(t,d.join(r,"entry.mjs"),320),g(s,d.join(r,"daemon.mjs"),320);let a=Be(e,r,o.linuxSandbox,n),l=o.browserRuntime?Pe(e,r,o.browserRuntime):"disabled";Ie(d.join(r,"manifest.json"),`${JSON.stringify({type:"headless-npm-daemon",version:o.version,edition:o.edition,browserRuntime:l,linuxSandbox:a},null,2)}
16
+ `,256)}function y(e,r){Te(e,r);let o=d.join(e.scripts,"daemon.mjs");u.existsSync(o)||g(d.join(r,"daemon.mjs"),o,320)}function Fe(e,r,o={platform:process.platform,arch:process.arch}){let n=x(e,r,o);return y(r,n.releaseDir),n}var Q=Ue(import.meta.url),ee={siboot:"/data/siboot",coboot:"/data/coboot"},We=15e3,K=7e3,Ve=73,z=20,Je=["BACKEND_URL","WS_URL","SIBOOT_URL","MCP_CREDENTIAL_API_BASE_URL","SKILL_CREDENTIAL_API_BASE_URL"],Ge=["HEADLESS_BWRAP_EXECUTABLE","HEADLESS_BWRAP_LOADER","HEADLESS_BWRAP_LIBRARY_DIR"];function Xe(e,r){let o=e||ee[r],n=i.resolve(o);if(!i.isAbsolute(o)||n===i.parse(n).root)throw new Error(`SIBOOT_DATA_ROOT must be a non-root absolute path: ${e}`);return n}function Ye(e=process.env.SIBOOT_DATA_ROOT,r="siboot"){if(!(r in ee))throw new Error(`unsupported headless edition: ${r}`);let o=Xe(e,r),n=i.join(o,".siboot");return{edition:r,root:o,home:n,releases:i.join(n,"releases"),current:i.join(n,"current"),scripts:i.join(n,"scripts"),run:i.join(n,"run"),logs:i.join(n,"logs"),workspace:i.join(o,"workspace"),credential:i.join(n,"headless-device-credential.json"),authSession:i.join(n,"headless-auth-session.json")}}function Ke(e,r){return`npx -y '${e}@${r}' install-browser`}function R(e){try{let r=Number.parseInt(m.readFileSync(e,"utf8").trim(),10);return Number.isInteger(r)&&r>0?r:null}catch{return null}}function h(e,r=process.platform==="linux"?"/proc":""){if(!e)return!1;try{process.kill(e,0)}catch{return!1}if(!r)return!0;try{let o=m.readFileSync(i.join(r,String(e),"stat"),"utf8"),n=o.lastIndexOf(") ")+2;return n<2||o[n]!=="Z"}catch{return!0}}function re(e,r){let o="";try{o=m.readFileSync(`/proc/${e}/cmdline`,"utf8").replaceAll("\0"," ").trim()}catch{}if(!o)try{o=Me("ps",["-ww","-p",String(e),"-o","command="],{encoding:"utf8"}).trim()}catch{return!1}return r.some(n=>[n,C(n)].some(t=>o.includes(t)))}var O=e=>new Promise(r=>setTimeout(r,e));function L(e=process.versions.node){let r=Number.parseInt(String(e).split(".")[0],10);if(!Number.isInteger(r)||r<z)throw new Error(`Node.js >=${z} is required; current version is ${e}`)}async function Z(e,r,o){let n=R(e);if(!h(n)){try{m.unlinkSync(e)}catch{}return!1}if(!re(n,r))throw new Error(`refusing to stop unrelated process from stale ${i.basename(e)}: ${n}`);process.kill(n,"SIGTERM");let t=Date.now()+K;for(;Date.now()<t&&h(n);)await O(100);if(h(n))throw new Error(`${o} did not stop within ${K/1e3} seconds: ${n}`);return R(e)===n&&m.rmSync(e,{force:!0}),!0}async function b(e){let r=await Z(i.join(e.run,"daemon.pid"),[i.join(e.current,"daemon.mjs"),i.join(e.scripts,"daemon.mjs")],"daemon"),o=await Z(i.join(e.run,"node.pid"),[i.join(e.current,"entry.mjs")],"client");return r||o}function ze(e,r,o=process.env){let n={...o,CU_EDITION:e.edition,NO_ELECTRON:"1",CLIENT_TYPE:"anydev",SIBOOT_BASE_DIR:e.home,SIBOOT_DATA_ROOT:e.root,HEADLESS_DATA_DIR:e.root,SIBOOT_DATA_DIR:e.root,HEADLESS_DEVICE_CREDENTIAL_PATH:e.credential,HEADLESS_AUTH_SESSION_PATH:e.authSession,HEADLESS_DEVICE_REQUIRE_AUTH:"1",HEADLESS_PREWARM_DEFAULT_SESSION:"0",WORKSPACE:e.workspace,ANYDEV_UPDATE_ENABLED:"0"};r?n.HEADLESS_DEVICE_TOKEN=r:delete n.HEADLESS_DEVICE_TOKEN,delete n.CLIENT_UID,delete n.HEADLESS_DEVICE_FORCE_ENROLL;for(let t of Ge)delete n[t];if(e.edition==="coboot"){let t=i.join(e.current,"native");n.HEADLESS_BWRAP_EXECUTABLE=i.join(t,"bwrap"),n.HEADLESS_BWRAP_LOADER=i.join(t,"ld-musl"),n.HEADLESS_BWRAP_LIBRARY_DIR=i.join(t,"lib"),n.HEADLESS_BROWSER_RUNTIME="system-or-guided-install",n.PLAYWRIGHT_BROWSERS_PATH=B(e.home,o)}else delete n.HEADLESS_BROWSER_RUNTIME,delete n.HEADLESS_BROWSER_INSTALL_COMMAND;if(n.HEADLESS_ALLOW_ENDPOINT_OVERRIDE!=="1")for(let t of Je)delete n[t];return n}async function Ze(e,r=We){let o=Date.now()+r,n=!1;for(;Date.now()<o;){let t=R(i.join(e.run,"daemon.pid"));if(!h(t))throw new Error("daemon exited during client startup");let s=null;try{s=JSON.parse(m.readFileSync(i.join(e.run,"status.json"),"utf8"))}catch{}if(s?.client_ready===!0&&h(s.client_pid))return;if(s?.client_exit_code!=null&&!s?.client_pid){if(s.client_exit_code===Ve){n=!0,await O(100);continue}throw new Error(`client exited during startup with code ${s.client_exit_code}`)}await O(100)}throw n?new Error("another Headless Driver still holds the machine lock after startup retries"):new Error(`client did not become ready within ${r}ms`)}function T(e,r=""){let o=e.edition==="coboot"?"coboot-headless":"siboot-headless";if(!m.existsSync(e.credential)&&!r)throw new Error(`device credential is missing; run ${o} <device-token> first`);let n=m.existsSync(i.join(e.current,"daemon.mjs"))?i.join(e.current,"daemon.mjs"):i.join(e.scripts,"daemon.mjs"),t=m.existsSync(i.join(e.current,"entry.mjs")),s=m.existsSync(n);if(!t||!s)throw new Error(`runtime is not installed (entry=${t}, daemon=${s}); run ${o} <device-token> first`);return n}async function A(e,r="",o=process.env){P(e);let n=T(e,r),t=m.existsSync(e.credential)?"":r,s=i.join(e.logs,"daemon",`${new Date().toISOString().slice(0,10)}.log`),a=m.openSync(s,"a",384);q(process.execPath,[n],{detached:!0,env:ze(e,t,o),stdio:["ignore",a,a]}).unref(),m.closeSync(a),await O(500);let c=R(i.join(e.run,"daemon.pid"));if(!h(c))throw new Error(`daemon failed to start; check ${s}`);return await Ze(e),{pid:c,logFile:s}}function k(e){try{let r=JSON.parse(m.readFileSync(i.join(e.current,"manifest.json"),"utf8"));return typeof r.version=="string"?r.version:null}catch{return null}}async function qe(e,r={}){return L(r.nodeVersion),T(e),await b(e),A(e,"",r.baseEnv||process.env)}async function ne(e,r,o={}){L(o.nodeVersion);let n=o.token||"";if(!m.existsSync(r.credential)&&!n){let l=r.edition==="coboot"?"coboot-headless":"siboot-headless";throw new Error(`device credential is missing; run ${l} <device-token> first`)}let t=j(r),s=k(r),a=x(e,r,o.runtime||{platform:process.platform,arch:process.arch});await b(r),y(r,a.releaseDir);try{let l=await A(r,n,o.baseEnv||process.env);return{previousVersion:s,installed:a,started:l}}catch(l){if(await b(r).catch(()=>{}),!t)throw l;y(r,t);try{await A(r,"",o.baseEnv||process.env)}catch(c){throw new AggregateError([l,c],`headless update failed and the previous runtime could not be restarted: ${c instanceof Error?c.message:String(c)}`)}throw new Error(`headless update failed; restored ${s||"the previous version"}`,{cause:l})}}async function Qe(e,r,o,n={}){return ne(e,r,{...n,token:o})}async function er(e,r,o={}){return T(r),ne(e,r,o)}function rr(e){let r=R(i.join(e.run,"daemon.pid")),o=null;try{o=JSON.parse(m.readFileSync(i.join(e.run,"status.json"),"utf8"))}catch{}console.log(JSON.stringify({running:h(r),pid:r,current:m.existsSync(e.current),status:o},null,2))}async function nr(e){let r=i.join(e.logs,"daemon",`${new Date().toISOString().slice(0,10)}.log`);if(!m.existsSync(r))throw new Error(`log file not found: ${r}`);let o=q("tail",["-n","100","-f",r],{stdio:"inherit"});await new Promise((n,t)=>{o.once("error",t),o.once("exit",s=>s===0?n():t(new Error(`tail exited ${s}`)))})}function v(e,r=e.version){let o={...process.env};return e.browserRuntime&&(o.HEADLESS_BROWSER_INSTALL_COMMAND=Ke(e.packageName,r)),o}async function or(e,r,o,n){L();let t=j(o),s=k(o),a=x(e,o);await b(o);try{return await Y(o,async()=>{y(o,a.releaseDir);let l=await A(o,n,v(r));return{installed:a,started:l}},()=>b(o))}catch(l){if(!t)throw l;y(o,t);try{await A(o,"",v(r,s||r.version))}catch(c){throw new AggregateError([l,c],`force enrollment failed and the previous runtime could not be restarted: ${c instanceof Error?c.message:String(c)}`)}throw l}}async function tr(){let e=I(process.argv.slice(2)),r=i.basename(process.argv[1]||"siboot-headless"),o=i.dirname(Q),n=D(o),t=Ye(process.env.SIBOOT_DATA_ROOT,n.edition);if(e.command==="help")return F(r,t.root,!!n.browserRuntime);if(e.command==="install-browser"){if(!n.browserRuntime)throw new Error("install-browser is available in the Coboot headless npm package only");return J(o,t)}let s=n.edition==="coboot"?"Coboot":"Siboot";if(e.command==="status")return rr(t);if(e.command==="logs")return nr(t);if(e.command==="stop"){let c=await b(t);console.log(c?`${s} Headless stopped`:`${s} Headless is not running`);return}if(e.command==="start"){L(),T(t);let c=R(i.join(t.run,"daemon.pid")),E=[i.join(t.current,"daemon.mjs"),i.join(t.scripts,"daemon.mjs")];if(h(c)&&re(c,E)){console.log(`${s} Headless is already running (pid=${c})`);return}await b(t);let S=await A(t,"",v(n,k(t)||n.version));console.log(`${s} Headless started in background (pid=${S.pid})`),console.log(`Logs: ${S.logFile}`);return}if(e.command==="restart"){let c=await qe(t,{baseEnv:v(n,k(t)||n.version)});console.log(`${s} Headless restarted in background (pid=${c.pid})`),console.log(`Logs: ${c.logFile}`);return}if(e.command==="update"){let{previousVersion:c,installed:E,started:S}=await er(o,t,{baseEnv:v(n)});console.log(`${s} Headless updated ${c||"unknown"} -> ${E.version} (pid=${S.pid})`),console.log(`Logs: ${S.logFile}`);return}let{installed:a,started:l}=e.forceEnroll?await or(o,n,t,e.token):await Qe(o,t,e.token||"",{baseEnv:v(n)});console.log(`${s} Headless ${a.version} started in background (pid=${l.pid})`),e.forceEnroll&&console.log("Device credential replaced by force enrollment"),console.log(`Logs: ${l.logFile}`)}function C(e){try{return m.realpathSync(e)}catch{return i.resolve(e)}}process.argv[1]&&C(process.argv[1])===C(Q)&&tr().catch(e=>{let r=i.basename(process.argv[1]||"headless-client");console.error(`${r}: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{L as assertSupportedNodeVersion,ze as createDaemonEnv,Qe as installAndStartHeadlessRuntime,Fe as installHeadlessRuntime,h as isRunning,I as parseHeadlessCliArgs,D as readPackageMetadata,Ye as resolveHeadlessLayout,qe as restartHeadlessRuntime,A as startHeadlessRuntime,b as stopHeadlessRuntime,er as updateHeadlessRuntime,T as validateHeadlessRuntimeStart};
package/daemon.mjs CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import r from"fs";import F from"os";import t from"path";import{spawn as h}from"child_process";var d=process.env.SIBOOT_BASE_DIR,f=t.join(d,"current"),A=process.env.WORKSPACE||"/data/siboot/workspace",S=t.join(d,"run"),l=t.join(d,"logs"),I=t.join(f,"entry.mjs"),T=t.join(d,"scripts","updater.mjs"),x=t.join(S,"daemon.pid"),N=t.join(S,"node.pid"),g=t.join(S,"updater.pid"),U=t.join(S,"status.json"),P=Number.parseInt(process.env.ANYDEV_CLIENT_RESTART_DELAY_MS||"1000",10),G=Number.parseInt(process.env.ANYDEV_UPDATER_RESTART_DELAY_MS||"10000",10),y=Number.parseInt(process.env.ANYDEV_LOG_RETENTION_DAYS||"4",10),o=null,c=null,_=!1,v=0,L=0,m=!1,D=null,M=new Date().toISOString();function V(){for(let e of[S,A,t.join(l,"daemon"),t.join(l,"client"),t.join(l,"update")])r.mkdirSync(e,{recursive:!0})}function b(){return new Date().toISOString().slice(0,10)}function B(e){return t.join(l,e,`${b()}.log`)}function s(e,n){let i=`${new Date().toISOString()} [${e}] ${n}
3
- `;r.appendFile(B(e),i,()=>{}),e==="daemon"&&process.stdout.write(i)}function k(e){try{return JSON.parse(r.readFileSync(e,"utf8"))}catch{return null}}function H(e,n){r.mkdirSync(t.dirname(e),{recursive:!0}),r.writeFileSync(e,JSON.stringify(n,null,2),"utf8")}function E(e){try{r.unlinkSync(e)}catch{}}function R(){let e=k(t.join(f,"manifest.json"));if(typeof e?.version=="string"&&e.version.trim())return e.version.trim();try{return t.basename(r.realpathSync(f))}catch{return process.env.APP_VERSION||"unknown"}}function J(){let e=process.env.HEADLESS_DEVICE_CREDENTIAL_PATH||process.env.SIBOOT_DEVICE_CREDENTIAL_PATH;if(e&&e.trim())return t.resolve(e.trim());let n=process.env.HEADLESS_DATA_DIR||process.env.SIBOOT_DATA_DIR||t.join(F.homedir(),".siboot");return t.join(n,"headless-device-credential.json")}function K(){try{return r.existsSync(J())}catch{return!1}}function a(e={}){H(U,{pid:process.pid,client_pid:o?.pid||null,updater_pid:c?.pid||null,client_ready:m,client_ready_at:D,client_start_count:v,updater_start_count:L,version:R(),current_dir:f,state:_?"stopping":"running",started_at:M,updated_at:new Date().toISOString(),...e})}function W(e,n){let i=p=>{let u=Buffer.from(p);r.appendFile(B(n),u,()=>{})};e.stdout?.on("data",i),e.stderr?.on("data",i)}function $(){if(!Number.isFinite(y)||y<=0)return;let e=Date.now();for(let n of["daemon","client","update"]){let i=t.join(l,n);if(r.existsSync(i))for(let p of r.readdirSync(i)){let u=t.join(i,p),O=null;try{O=r.statSync(u)}catch{continue}if(!O.isFile())continue;e-O.mtimeMs>y*24*60*60*1e3&&E(u)}}}function w(){if(_||o)return;if(!r.existsSync(I)){s("daemon",`client entry missing entry=${I}`),a({state:"client_entry_missing"}),setTimeout(w,P).unref?.();return}v+=1,m=!1,D=null;let e={...process.env,APP_VERSION:R(),CLIENT_TYPE:process.env.CLIENT_TYPE||"anydev",SIBOOT_BASE_DIR:d,SIBOOT_CURRENT_DIR:f,SIBOOT_RUN_DIR:S,SIBOOT_LOG_DIR:l};K()&&(delete e.HEADLESS_DEVICE_TOKEN,delete process.env.HEADLESS_DEVICE_TOKEN),s("daemon",`starting client entry=${I} cwd=${A} version=${R()} start_count=${v}`),o=h(process.execPath,[I],{cwd:A,env:e,stdio:["ignore","pipe","pipe","ipc"]}),r.writeFileSync(N,String(o.pid)),W(o,"client"),a(),o.on("message",n=>{n?.type==="headless-client-ready"&&(m=!0,D=new Date().toISOString(),a(),s("daemon",`client ready pid=${o?.pid||""}`))}),o.on("error",n=>{s("daemon",`client spawn error error=${n.message}`)}),o.on("exit",(n,i)=>{s("daemon",`client exited pid=${o?.pid||""} code=${n} signal=${i}`),o=null,m=!1,D=null,E(N),a({client_exit_code:n,client_exit_signal:i}),_||setTimeout(w,P).unref?.()})}function Y(){if(!(_||c||process.env.ANYDEV_UPDATE_ENABLED==="0")){if(!r.existsSync(T)){s("daemon",`updater entry missing entry=${T}`);return}L+=1,s("daemon",`starting updater entry=${T} start_count=${L}`),c=h(process.execPath,[T],{cwd:d,env:{...process.env,CLIENT_TYPE:"anydev",SIBOOT_BASE_DIR:d,SIBOOT_CURRENT_DIR:f,SIBOOT_RUN_DIR:S,SIBOOT_LOG_DIR:l},stdio:["ignore","inherit","inherit"]}),r.writeFileSync(g,String(c.pid)),a(),c.on("error",e=>{s("daemon",`updater spawn error error=${e.message}`)}),c.on("exit",(e,n)=>{s("daemon",`updater exited pid=${c?.pid||""} code=${e} signal=${n}`),c=null,E(g),a({updater_exit_code:e,updater_exit_signal:n}),_||setTimeout(Y,G).unref?.()})}}function C(e,n){return e?.pid?(s("daemon",`stopping ${n} pid=${e.pid}`),new Promise(i=>{let p=!1,u=()=>{p||(p=!0,i())};e.once("exit",u);try{e.kill("SIGTERM")}catch{u();return}setTimeout(()=>{if(!p){try{e.kill("SIGKILL")}catch{}u()}},5e3).unref?.()})):Promise.resolve()}async function j(e){_||(_=!0,s("daemon",`shutdown signal=${e}`),a({state:"stopping",signal:e}),await Promise.all([C(c,"updater"),C(o,"client")]),E(g),E(N),E(x),a({state:"stopped",signal:e}),process.exit(0))}function Q(){V(),r.writeFileSync(x,String(process.pid)),process.on("SIGTERM",()=>{j("SIGTERM")}),process.on("SIGINT",()=>{j("SIGINT")}),s("daemon",`started base=${d} version=${R()}`),a({state:"starting"}),$(),w(),Y(),setInterval($,3600*1e3)}Q();
2
+ import r from"fs";import V from"net";import b from"os";import i from"path";import{spawn as B}from"child_process";var _=process.env.SIBOOT_BASE_DIR,E=i.join(_,"current"),N=process.env.WORKSPACE||"/data/siboot/workspace",S=i.join(_,"run"),f=i.join(_,"logs"),I=i.join(E,"entry.mjs"),m=i.join(_,"scripts","updater.mjs"),F=i.join(S,"daemon.pid"),h=i.join(S,"node.pid"),$=i.join(S,"updater.pid"),G=i.join(S,"status.json"),Y=73,H=5e3,K=3e4,k=Number.parseInt(process.env.ANYDEV_CLIENT_RESTART_DELAY_MS||"1000",10),J=Number.parseInt(process.env.ANYDEV_UPDATER_RESTART_DELAY_MS||"10000",10),A=Number.parseInt(process.env.ANYDEV_LOG_RETENTION_DAYS||"4",10),s=null,d=null,u=!1,L=0,v=0,T=!1,D=null,y=null,p=0,g=null,W=new Date().toISOString();async function X(){let e=V.createServer(n=>n.destroy()),t=process.platform==="linux"?{path:"\0siboot-anydev-daemon-v1"}:{host:"127.0.0.1",port:43880,exclusive:!0};await new Promise((n,c)=>{e.once("error",c),e.listen(t,()=>{e.removeListener("error",c),n()})})}function q(){for(let e of[S,N,i.join(f,"daemon"),i.join(f,"client"),i.join(f,"update")])r.mkdirSync(e,{recursive:!0})}function Q(){return new Date().toISOString().slice(0,10)}function M(e){return i.join(f,e,`${Q()}.log`)}function o(e,t){let n=`${new Date().toISOString()} [${e}] ${t}
3
+ `;r.appendFile(M(e),n,()=>{}),e==="daemon"&&process.stdout.write(n)}function z(e){try{return JSON.parse(r.readFileSync(e,"utf8"))}catch{return null}}function Z(e,t){r.mkdirSync(i.dirname(e),{recursive:!0}),r.writeFileSync(e,JSON.stringify(t,null,2),"utf8")}function ee(e){try{r.unlinkSync(e)}catch{}}function w(e,t){if(t)try{r.readFileSync(e,"utf8").trim()===String(t)&&r.unlinkSync(e)}catch(n){n.code!=="ENOENT"&&o("daemon",`pid cleanup failed file=${e} error=${n.message}`)}}function O(){let e=z(i.join(E,"manifest.json"));if(typeof e?.version=="string"&&e.version.trim())return e.version.trim();try{return i.basename(r.realpathSync(E))}catch{return process.env.APP_VERSION||"unknown"}}function ne(){let e=process.env.HEADLESS_DEVICE_CREDENTIAL_PATH||process.env.SIBOOT_DEVICE_CREDENTIAL_PATH;if(e&&e.trim())return i.resolve(e.trim());let t=process.env.HEADLESS_DATA_DIR||process.env.SIBOOT_DATA_DIR||i.join(b.homedir(),".siboot");return i.join(t,"headless-device-credential.json")}function te(){try{return r.existsSync(ne())}catch{return!1}}function l(e={}){Z(G,{pid:process.pid,client_pid:s?.pid||null,updater_pid:d?.pid||null,client_ready:T,client_ready_at:D,client_version:y,client_lock_conflict_count:p,client_retry_at:g,client_start_count:L,updater_start_count:v,version:O(),current_dir:E,state:u?"stopping":p>0?"client_lock_conflict":"running",started_at:W,updated_at:new Date().toISOString(),...e})}function ie(e,t){let n=c=>{let a=Buffer.from(c);r.appendFile(M(t),a,()=>{})};e.stdout?.on("data",n),e.stderr?.on("data",n)}function P(){if(!Number.isFinite(A)||A<=0)return;let e=Date.now();for(let t of["daemon","client","update"]){let n=i.join(f,t);if(r.existsSync(n))for(let c of r.readdirSync(n)){let a=i.join(n,c),R=null;try{R=r.statSync(a)}catch{continue}if(!R.isFile())continue;e-R.mtimeMs>A*24*60*60*1e3&&ee(a)}}}function C(){if(u||s)return;if(!r.existsSync(I)){o("daemon",`client entry missing entry=${I}`),l({state:"client_entry_missing"}),setTimeout(C,k).unref?.();return}L+=1,T=!1,D=null,y=O(),g=null;let e={...process.env,APP_VERSION:y,CLIENT_TYPE:process.env.CLIENT_TYPE||"anydev",SIBOOT_BASE_DIR:_,SIBOOT_CURRENT_DIR:E,SIBOOT_RUN_DIR:S,SIBOOT_LOG_DIR:f};te()&&(delete e.HEADLESS_DEVICE_TOKEN,delete process.env.HEADLESS_DEVICE_TOKEN),o("daemon",`starting client entry=${I} cwd=${N} version=${O()} start_count=${L}`),s=B(process.execPath,[I],{cwd:N,env:e,stdio:["ignore","pipe","pipe","ipc"]});let t=s;r.writeFileSync(h,String(s.pid)),ie(s,"client"),l(),s.on("message",n=>{n?.type!=="headless-client-ready"||s!==t||(p>0&&o("daemon",`client lock recovered conflicts=${p} pid=${t.pid}`),p=0,T=!0,D=new Date().toISOString(),l(),o("daemon",`client ready pid=${s?.pid||""}`))}),s.on("error",n=>{o("daemon",`client spawn error error=${n.message}`)}),s.on("exit",(n,c)=>{o("daemon",`client exited pid=${s?.pid||""} code=${n} signal=${c}`),s=null,T=!1,D=null,y=null,w(h,t.pid);let a=re(n);g=u?null:new Date(Date.now()+a).toISOString(),l({client_exit_code:n,client_exit_signal:c}),u||setTimeout(C,a).unref?.()})}function re(e){return e!==Y?k:(p+=1,p===1&&o("daemon","client lock conflict; retrying with bounded backoff"),Math.min(H*2**Math.min(p-1,3),K))}function U(){if(u||d||process.env.ANYDEV_UPDATE_ENABLED==="0")return;if(!r.existsSync(m)){o("daemon",`updater entry missing entry=${m}`);return}v+=1,o("daemon",`starting updater entry=${m} start_count=${v}`),d=B(process.execPath,[m],{cwd:_,env:{...process.env,CLIENT_TYPE:"anydev",SIBOOT_BASE_DIR:_,SIBOOT_CURRENT_DIR:E,SIBOOT_RUN_DIR:S,SIBOOT_LOG_DIR:f},stdio:["ignore","inherit","inherit"]});let e=d;r.writeFileSync($,String(d.pid)),l(),d.on("error",t=>{o("daemon",`updater spawn error error=${t.message}`)}),d.on("exit",(t,n)=>{o("daemon",`updater exited pid=${d?.pid||""} code=${t} signal=${n}`),d=null,w($,e.pid),l({updater_exit_code:t,updater_exit_signal:n}),u||setTimeout(U,J).unref?.()})}function j(e,t){return e?.pid?(o("daemon",`stopping ${t} pid=${e.pid}`),new Promise(n=>{let c=!1,a=()=>{c||(c=!0,n())};e.once("exit",a);try{e.kill("SIGTERM")}catch{a();return}setTimeout(()=>{if(!c){try{e.kill("SIGKILL")}catch{}a()}},5e3).unref?.()})):Promise.resolve()}async function x(e){u||(u=!0,o("daemon",`shutdown signal=${e}`),l({state:"stopping",signal:e}),await Promise.all([j(d,"updater"),j(s,"client")]),w(F,process.pid),l({state:"stopped",signal:e}),process.exit(0))}async function oe(){await X(),q(),r.writeFileSync(F,String(process.pid)),process.on("SIGTERM",()=>{x("SIGTERM")}),process.on("SIGINT",()=>{x("SIGINT")}),o("daemon",`started base=${_} version=${O()}`),l({state:"starting"}),P(),C(),U(),setInterval(P,3600*1e3)}oe().catch(e=>{let t=e.code==="EADDRINUSE";process.stderr.write(`[daemon] ${t?"another daemon is already running":e.message}
4
+ `),process.exit(t?Y:1)});