@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.
- package/README.md +12 -12
- package/daemon.mjs +3 -2
- package/entry.mjs +426 -364
- 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.
|
|
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.
|
|
16
|
-
npx -y @coboot/headless@0.2.2-rc.
|
|
17
|
-
npx -y @coboot/headless@0.2.2-rc.
|
|
18
|
-
npx -y @coboot/headless@0.2.2-rc.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
137
|
-
sha256sum dist-headless/coboot-headless-0.2.2-rc.
|
|
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
|
|
3
|
-
`;r.appendFile(
|
|
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)});
|