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

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.
Files changed (4) hide show
  1. package/README.md +12 -12
  2. package/daemon.mjs +3 -2
  3. package/entry.mjs +426 -364
  4. package/package.json +1 -4
package/README.md CHANGED
@@ -4,7 +4,7 @@ Node.js 20+ 的无 GUI 后台运行包。本包不携带浏览器二进制,也
4
4
  首次启动只需一次性设备 token:
5
5
 
6
6
  ```bash
7
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
7
+ npx -y @coboot/headless@0.2.2-rc.3 '<device-token>'
8
8
  ```
9
9
 
10
10
  命令会把运行时安装到 `/data/coboot/.siboot`,在客户端鉴权和运行时初始化完成后启动
@@ -12,10 +12,10 @@ detached daemon 并退出。不需要设置 `BACKEND_URL`、`CLIENT_UID`。终
12
12
  目标机安装 Chromium。
13
13
 
14
14
  ```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
15
+ npx -y @coboot/headless@0.2.2-rc.3 status
16
+ npx -y @coboot/headless@0.2.2-rc.3 logs
17
+ npx -y @coboot/headless@0.2.2-rc.3 restart
18
+ npx -y @coboot/headless@0.2.2-rc.3 stop
19
19
  ```
20
20
 
21
21
  默认数据根目录可通过 `SIBOOT_DATA_ROOT` 修改。该设置不会写入配置,首次安装以及后续的
@@ -30,13 +30,13 @@ Coboot daemon 默认禁用 updater 和浏览器预热,第一次调用 `browser
30
30
 
31
31
  ```bash
32
32
  HEADLESS_BROWSER_EXECUTABLE=/absolute/path/to/chrome \
33
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
33
+ npx -y @coboot/headless@0.2.2-rc.3 '<device-token>'
34
34
  ```
35
35
 
36
36
  需要安装浏览器时,由用户执行:
37
37
 
38
38
  ```bash
39
- npx -y @coboot/headless@0.2.2-rc.2 install-browser
39
+ npx -y @coboot/headless@0.2.2-rc.3 install-browser
40
40
  ```
41
41
 
42
42
  该命令不需要设备 token,也不要求 root。它会先检查现有浏览器并执行启动验证;浏览器已经可用时
@@ -74,7 +74,7 @@ dnf/yum 先试仓库中的 `chromium`,x86_64 回退到官方 RPM 并关闭 wea
74
74
  已有设备凭证无效或需要切换设备身份时,显式重新注册:
75
75
 
76
76
  ```bash
77
- npx -y @coboot/headless@0.2.2-rc.2 --force-enroll '<device-token>'
77
+ npx -y @coboot/headless@0.2.2-rc.3 --force-enroll '<device-token>'
78
78
  ```
79
79
 
80
80
  命令会先停止 daemon 并临时备份旧凭证;新凭证和客户端均就绪后才完成替换,失败时恢复旧凭证。
@@ -111,7 +111,7 @@ HTTP 与 WebSocket 必须同域并与设备 token 匹配:
111
111
  HEADLESS_ALLOW_ENDPOINT_OVERRIDE=1 \
112
112
  BACKEND_URL=https://example.com \
113
113
  WS_URL=wss://example.com/wsschat \
114
- npx -y @coboot/headless@0.2.2-rc.2 '<device-token>'
114
+ npx -y @coboot/headless@0.2.2-rc.3 '<device-token>'
115
115
  ```
116
116
 
117
117
  WebSocket 覆盖变量名是 `WS_URL`,不是 `WSS_URL`。
@@ -125,7 +125,7 @@ endpoint 覆盖同样不会写入配置;后续执行 `restart` 时必须再次
125
125
  CU_EDITION=coboot \
126
126
  PKG_NAME=@coboot/headless \
127
127
  PKG_BIN_NAME=coboot-headless \
128
- PKG_VERSION=0.2.2-rc.2 \
128
+ PKG_VERSION=0.2.2-rc.3 \
129
129
  pnpm build:headless:npm
130
130
  ```
131
131
 
@@ -133,8 +133,8 @@ pnpm build:headless:npm
133
133
 
134
134
  ```bash
135
135
  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
136
+ tar -xOf dist-headless/coboot-headless-0.2.2-rc.3.tgz package/package.json
137
+ sha256sum dist-headless/coboot-headless-0.2.2-rc.3.tgz
138
138
  ```
139
139
 
140
140
 
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)});