@cocorograph/hub-agent 0.8.66 → 0.8.67
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/bin/hub-agent.mjs +6 -9
- package/package.json +1 -1
- package/scripts/install.sh +4 -1
- package/src/service-install.mjs +140 -15
package/bin/hub-agent.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import { loadPlugins } from "../src/plugin-loader.mjs"
|
|
|
38
38
|
import { uninstallPlugin } from "../src/plugin-uninstall.mjs"
|
|
39
39
|
import {
|
|
40
40
|
canSuggestForcedRestart,
|
|
41
|
+
formatInstallServiceReport,
|
|
41
42
|
installService,
|
|
42
43
|
restartService,
|
|
43
44
|
restartWithPolicy,
|
|
@@ -215,16 +216,12 @@ program
|
|
|
215
216
|
.action(async (opts) => {
|
|
216
217
|
try {
|
|
217
218
|
const r = await installService({ bin: opts.bin })
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.
|
|
222
|
-
console.log(
|
|
223
|
-
} else {
|
|
224
|
-
console.log(`unit: ${r.unit}`)
|
|
225
|
-
console.log("確認: systemctl --user status hub-agent.service")
|
|
219
|
+
// 表示と警告の判断は formatInstallServiceReport (テスト可能な純関数) が持つ。
|
|
220
|
+
// 順序を崩さないよう、種別つきの 1 本の配列をそのまま流す。
|
|
221
|
+
for (const { stream, text } of formatInstallServiceReport(r)) {
|
|
222
|
+
if (stream === "warn") console.warn(text)
|
|
223
|
+
else console.log(text)
|
|
226
224
|
}
|
|
227
|
-
console.log("ログ: ~/.hub/agent.log")
|
|
228
225
|
} catch (err) {
|
|
229
226
|
console.error(`install-service failed: ${err.message}`)
|
|
230
227
|
process.exit(1)
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -1347,8 +1347,11 @@ verify_setup() {
|
|
|
1347
1347
|
done < <(_hub_target_clis)
|
|
1348
1348
|
|
|
1349
1349
|
# 4. OS サービス起動 (Darwin 限定の確認。Linux は systemctl --user で別途)
|
|
1350
|
+
# ⚠️ 判定は `launchctl print` で行う。install-service 側が登録の出現を待つのに使う
|
|
1351
|
+
# 道具と揃えるため (src/service-install.mjs の waitUntilBootedIn)。`launchctl list | grep`
|
|
1352
|
+
# に戻すと、待つ側と確認する側で別の状態を見ることになり誤検知が復活する。
|
|
1350
1353
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
1351
|
-
if launchctl
|
|
1354
|
+
if launchctl print "gui/$(id -u)/co.cocorograph.hub-agent" >/dev/null 2>&1; then
|
|
1352
1355
|
color_ok "launchd service 起動中 (co.cocorograph.hub-agent)"
|
|
1353
1356
|
else
|
|
1354
1357
|
color_warn "launchd service が見つかりません"
|
package/src/service-install.mjs
CHANGED
|
@@ -154,6 +154,29 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
|
154
154
|
* @returns {Promise<boolean>} 消えたら true / timeout なら false
|
|
155
155
|
*/
|
|
156
156
|
async function waitUntilBootedOut(uid, opts = {}) {
|
|
157
|
+
// 非 0 = 登録に存在しない = bootout 完了
|
|
158
|
+
return waitForRegistration(uid, false, opts)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* launchd の登録状態が `want` になるまで polling する。`waitUntilBootedOut` /
|
|
163
|
+
* `waitUntilBootedIn` の共通実装。
|
|
164
|
+
*
|
|
165
|
+
* ⚠️ 判定は `launchctl print` の終了コードで行う。`launchctl list | grep` は同じ状態を
|
|
166
|
+
* 別の道具で見ることになり、待つ側と確認する側がずれると誤検知が復活するため、
|
|
167
|
+
* hub-agent 側と `scripts/install.sh` の verify_setup で道具を揃えている。
|
|
168
|
+
*
|
|
169
|
+
* ⚠️ 成功条件は `(status === 0) === want` の 1 箇所だけで表す。`status` は spawn 自体が
|
|
170
|
+
* 失敗すると `null` になり得るので、「0 か否か」に正規化してから比較する
|
|
171
|
+
* (bootout 待ちでは null は「登録に存在しない」=完了、bootin 待ちでは「まだ現れない」
|
|
172
|
+
* =継続、という従来の挙動がそのまま保たれる)。
|
|
173
|
+
*
|
|
174
|
+
* @param {number} uid
|
|
175
|
+
* @param {boolean} want true = 登録に現れるまで待つ / false = 消えるまで待つ
|
|
176
|
+
* @param {{ timeoutMs?: number, intervalMs?: number, spawn?: Function, sleep?: Function, now?: Function }} [opts]
|
|
177
|
+
* @returns {Promise<boolean>} 期待状態になったら true / timeout なら false
|
|
178
|
+
*/
|
|
179
|
+
async function waitForRegistration(uid, want, opts = {}) {
|
|
157
180
|
const timeoutMs = opts.timeoutMs ?? 5000
|
|
158
181
|
const intervalMs = opts.intervalMs ?? 200
|
|
159
182
|
const spawn = opts.spawn ?? spawnSync
|
|
@@ -164,13 +187,33 @@ async function waitUntilBootedOut(uid, opts = {}) {
|
|
|
164
187
|
const r = spawn("launchctl", ["print", `gui/${uid}/${PLIST_LABEL}`], {
|
|
165
188
|
stdio: "ignore",
|
|
166
189
|
})
|
|
167
|
-
|
|
168
|
-
if (r.status !== 0) return true
|
|
190
|
+
if ((r.status === 0) === want) return true
|
|
169
191
|
await sleepFn(intervalMs)
|
|
170
192
|
}
|
|
171
193
|
return false
|
|
172
194
|
}
|
|
173
195
|
|
|
196
|
+
/**
|
|
197
|
+
* `launchctl bootstrap` / `kickstart -k` の直後は、launchd 側の登録が見えるまでに
|
|
198
|
+
* わずかな遅れがある。install-service がそこで返ってしまうと、直後に登録を確認する
|
|
199
|
+
* 呼び出し側 (`scripts/install.sh` の verify_setup 等) が「サービスが見つからない」と
|
|
200
|
+
* 誤検知する。本関数は対象 service が launchd の登録に現れるまで polling して待つ。
|
|
201
|
+
* `waitUntilBootedOut` の対称形。
|
|
202
|
+
*
|
|
203
|
+
* 判定手段の統一 (`launchctl print`) は `waitForRegistration` の注意書きを参照。
|
|
204
|
+
*
|
|
205
|
+
* (2026-09-09: ワンライナー完走時に毎回「launchd service が見つかりません」が出ていた
|
|
206
|
+
* 誤検知の修正。install-service は成功しており、agent も起動していた)
|
|
207
|
+
*
|
|
208
|
+
* @param {number} uid
|
|
209
|
+
* @param {{ timeoutMs?: number, intervalMs?: number }} [opts]
|
|
210
|
+
* @returns {Promise<boolean>} 現れたら true / timeout なら false
|
|
211
|
+
*/
|
|
212
|
+
async function waitUntilBootedIn(uid, opts = {}) {
|
|
213
|
+
// 0 = 登録に存在する
|
|
214
|
+
return waitForRegistration(uid, true, opts)
|
|
215
|
+
}
|
|
216
|
+
|
|
174
217
|
/**
|
|
175
218
|
* bootout 後の bootstrap を、過渡的失敗 (旧サービスの抜け残り等) に対して
|
|
176
219
|
* リトライする。`run` と違い最終的に失敗したときだけ throw する。
|
|
@@ -257,7 +300,52 @@ function detectHubAgentBin() {
|
|
|
257
300
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "bin", "hub-agent.mjs")
|
|
258
301
|
}
|
|
259
302
|
|
|
260
|
-
|
|
303
|
+
/**
|
|
304
|
+
* darwin の launchd 登録シーケンス:
|
|
305
|
+
* bootout → (登録が消えるまで待機) → bootstrap (リトライ) → kickstart → (登録が現れるまで待機)。
|
|
306
|
+
*
|
|
307
|
+
* - bootout は非同期なので、待たずに bootstrap するとレースで失敗し offline 化する
|
|
308
|
+
* (2026-05-30 の事故)。`waitUntilBootedOut` で確実に抜けてから bootstrap する。
|
|
309
|
+
* - kickstart は「既に走っている daemon を再起動」する用途。bootstrap + RunAtLoad で
|
|
310
|
+
* 既に起動済みのことがあり、その場合 kickstart 失敗は致命的でないので throw しない。
|
|
311
|
+
* - kickstart もまた launchd 側の登録が見えるまで待たない。ここで待たずに返すと、直後に
|
|
312
|
+
* 登録を確認する呼び出し側 (`scripts/install.sh` の verify_setup 等) が誤検知する
|
|
313
|
+
* (2026-09-09 の誤検知)。現れなかった場合も throw しない: bootstrap は成功しているので
|
|
314
|
+
* 致命ではなく、判断は呼び出し側の確認へ委ねる。
|
|
315
|
+
* ⚠️ ただし「確認できなかった」事実は捨てない。非 0 で終わると `scripts/install.sh` の
|
|
316
|
+
* `retry 2 hub-agent install-service` が再インストール + 再起動を走らせてしまうので、
|
|
317
|
+
* exit code は変えず `registered` で返し、CLI 側は警告だけ出す。
|
|
318
|
+
*
|
|
319
|
+
* ⚠️ この順序をソース文字列の走査で固定するのは**やめた** (2026-09-09 cross-review 3 巡目)。
|
|
320
|
+
* コメント化・別オブジェクトへの退避など、文字列一致では追い切れない後退が巡ごとに出たため、
|
|
321
|
+
* 依存を差し替えて**実際に順序と返り値を確認できる**形へ切り出してある。同ファイルの
|
|
322
|
+
* `restartWithPolicy` と同じ考え方。`deps` は本番では既定値のまま使う。
|
|
323
|
+
*
|
|
324
|
+
* @param {{uid: number, dest: string, bin: string}} target
|
|
325
|
+
* @param {{spawn?: Function, bootedOut?: Function, bootstrap?: Function, bootedIn?: Function}} [deps]
|
|
326
|
+
*/
|
|
327
|
+
export async function activateDarwinService({ uid, dest, bin }, deps = {}) {
|
|
328
|
+
const spawn = deps.spawn ?? spawnSync
|
|
329
|
+
const bootedOut = deps.bootedOut ?? waitUntilBootedOut
|
|
330
|
+
const bootstrap = deps.bootstrap ?? bootstrapWithRetry
|
|
331
|
+
const bootedIn = deps.bootedIn ?? waitUntilBootedIn
|
|
332
|
+
|
|
333
|
+
spawn("launchctl", ["bootout", `gui/${uid}`, dest], { stdio: "ignore" })
|
|
334
|
+
await bootedOut(uid)
|
|
335
|
+
await bootstrap(uid, dest)
|
|
336
|
+
spawn("launchctl", ["kickstart", "-k", `gui/${uid}/${PLIST_LABEL}`], {
|
|
337
|
+
stdio: "ignore",
|
|
338
|
+
})
|
|
339
|
+
const registered = await bootedIn(uid)
|
|
340
|
+
return { platform: "darwin", path: dest, label: PLIST_LABEL, bin, registered }
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {{bin?: string, activate?: Function}} [opts]
|
|
345
|
+
* `activate` は darwin の launchd 登録シーケンス。テストから差し替えるための口で、
|
|
346
|
+
* 本番では既定 (`activateDarwinService`) のまま使う。
|
|
347
|
+
*/
|
|
348
|
+
export async function installService({ bin, activate = activateDarwinService } = {}) {
|
|
261
349
|
const hubAgentBin = bin || detectHubAgentBin()
|
|
262
350
|
await ensureLogFile()
|
|
263
351
|
|
|
@@ -269,18 +357,7 @@ export async function installService({ bin } = {}) {
|
|
|
269
357
|
await fs.writeFile(dest, expanded, { mode: 0o644 })
|
|
270
358
|
|
|
271
359
|
const uid = ensureUnixUid()
|
|
272
|
-
|
|
273
|
-
// bootout は非同期なので待たずに bootstrap するとレースで失敗し offline 化する
|
|
274
|
-
// (2026-05-30 の事故)。waitUntilBootedOut で確実に抜けてから bootstrap する。
|
|
275
|
-
spawnSync("launchctl", ["bootout", `gui/${uid}`, dest], { stdio: "ignore" })
|
|
276
|
-
await waitUntilBootedOut(uid)
|
|
277
|
-
await bootstrapWithRetry(uid, dest)
|
|
278
|
-
// kickstart は「既に走っている daemon を再起動」する用途。bootstrap + RunAtLoad で
|
|
279
|
-
// 既に起動済みのことがあり、その場合 kickstart 失敗は致命的でないので throw しない。
|
|
280
|
-
spawnSync("launchctl", ["kickstart", "-k", `gui/${uid}/${PLIST_LABEL}`], {
|
|
281
|
-
stdio: "ignore",
|
|
282
|
-
})
|
|
283
|
-
return { platform: "darwin", path: dest, label: PLIST_LABEL, bin: hubAgentBin }
|
|
360
|
+
return activate({ uid, dest, bin: hubAgentBin })
|
|
284
361
|
}
|
|
285
362
|
|
|
286
363
|
if (process.platform === "linux") {
|
|
@@ -389,6 +466,50 @@ export async function restartWithPolicy({
|
|
|
389
466
|
return { mode: "deferred", result: await deferredRestart(), service }
|
|
390
467
|
}
|
|
391
468
|
|
|
469
|
+
/**
|
|
470
|
+
* install-service の結果を CLI 表示へ変換する。`restartWithPolicy` と同じく、
|
|
471
|
+
* CLI 側の判断をテスト可能な純関数へ切り出したもの (bin/hub-agent.mjs から使う)。
|
|
472
|
+
*
|
|
473
|
+
* ⚠️ 確認コマンドは launchd の登録を待つ側 (`waitForRegistration`) と
|
|
474
|
+
* `scripts/install.sh` の verify_setup に揃える。`launchctl list | grep` へ戻すと、
|
|
475
|
+
* 同じ状態を 3 箇所が別々の道具で見ることになり、誤検知が復活する。
|
|
476
|
+
*
|
|
477
|
+
* ⚠️ 警告は `registered === false` のときだけ。bootstrap 自体は成功しているので
|
|
478
|
+
* exit code は変えない (非 0 にすると `scripts/install.sh` の `retry 2` が
|
|
479
|
+
* 再インストール + 再起動を走らせてしまう)。linux は登録待ちを持たず `registered` を
|
|
480
|
+
* 返さないので、`undefined` で警告しないこと。
|
|
481
|
+
*
|
|
482
|
+
* @param {{platform: string, path: string, bin: string, label?: string, unit?: string, registered?: boolean}} result
|
|
483
|
+
* @returns {{stream: 'log'|'warn', text: string}[]} 出力すべき行を順序どおりに
|
|
484
|
+
*/
|
|
485
|
+
export function formatInstallServiceReport(result) {
|
|
486
|
+
const out = [
|
|
487
|
+
{ stream: "log", text: `installed: ${result.platform} ${result.path}` },
|
|
488
|
+
{ stream: "log", text: `bin: ${result.bin}` },
|
|
489
|
+
]
|
|
490
|
+
if (result.platform === "darwin") {
|
|
491
|
+
out.push({ stream: "log", text: `label: ${result.label}` })
|
|
492
|
+
out.push({
|
|
493
|
+
stream: "log",
|
|
494
|
+
text: "確認: launchctl print gui/$(id -u)/co.cocorograph.hub-agent",
|
|
495
|
+
})
|
|
496
|
+
if (result.registered === false) {
|
|
497
|
+
// ⚠️ 警告は確認コマンドの直後に置く (読む人が対処法とセットで見るため)。
|
|
498
|
+
// 種別つきの 1 本の配列で返すのは、この順序を stdout / stderr の分離と
|
|
499
|
+
// 両立させるため。呼び出し側で 2 本のリストへ分けると順序が崩れる。
|
|
500
|
+
out.push({
|
|
501
|
+
stream: "warn",
|
|
502
|
+
text: "warn: launchd への登録を確認できませんでした (待機 timeout)。上のコマンドで確認してください",
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
out.push({ stream: "log", text: `unit: ${result.unit}` })
|
|
507
|
+
out.push({ stream: "log", text: "確認: systemctl --user status hub-agent.service" })
|
|
508
|
+
}
|
|
509
|
+
out.push({ stream: "log", text: "ログ: ~/.hub/agent.log" })
|
|
510
|
+
return out
|
|
511
|
+
}
|
|
512
|
+
|
|
392
513
|
export async function uninstallService() {
|
|
393
514
|
if (process.platform === "darwin") {
|
|
394
515
|
const uid = ensureUnixUid()
|
|
@@ -498,6 +619,10 @@ export const _internal = {
|
|
|
498
619
|
linuxUnitPath,
|
|
499
620
|
repoTemplatesDir,
|
|
500
621
|
waitUntilBootedOut,
|
|
622
|
+
waitUntilBootedIn,
|
|
623
|
+
waitForRegistration,
|
|
624
|
+
formatInstallServiceReport,
|
|
625
|
+
activateDarwinService,
|
|
501
626
|
bootstrapWithRetry,
|
|
502
627
|
currentUsername,
|
|
503
628
|
enableLinger,
|