@cocorograph/hub-agent 0.5.23 → 0.5.25

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 (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/install.sh +234 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -21,7 +21,17 @@
21
21
 
22
22
  set -euo pipefail
23
23
 
24
- NODE_MIN_MAJOR=20
24
+ # Node.js のサポート範囲ポリシー(Active LTS のみ)。
25
+ # - 既存 node のメジャーが [MIN, MAX] に収まっていれば現状維持
26
+ # - 範囲外なら NODE_DEFAULT_BREW_FORMULA (最新 Active LTS) にアップ / ダウングレード
27
+ # 2026-05 時点の LTS スケジュール:
28
+ # - node 22: Active LTS → 2027/04 EoL
29
+ # - node 24: Active LTS → 2028/04 EoL(最新、サポート期間最長)
30
+ # - node 26: current(LTS ではない、SSL/CA 周りの問題報告あり)
31
+ NODE_MIN_MAJOR=22
32
+ NODE_MAX_MAJOR=24
33
+ NODE_DEFAULT_BREW_FORMULA="node@24"
34
+
25
35
  PACKAGE_NAME="@cocorograph/hub-agent"
26
36
  CLAUDE_CODE_PACKAGE="@anthropic-ai/claude-code"
27
37
 
@@ -204,6 +214,183 @@ ensure_npm_user_prefix() {
204
214
  done
205
215
  }
206
216
 
217
+ # =============================================================================
218
+ # Node TLS 環境の自動修復
219
+ #
220
+ # 背景: npm install で `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` が出る環境がある。
221
+ # 原因は「node は OS の信頼ストアを使わず、自分にコンパイル時に焼き込まれた
222
+ # CA リストだけで TLS 検証する」設計にある:
223
+ #
224
+ # - curl は macOS Keychain (or /etc/ssl/certs) を使う → 通る
225
+ # - node はバンドル CA のみ → 通らない
226
+ #
227
+ # テナント環境では以下のいずれかで MITM 的な証明書差し替えが起きうる:
228
+ # - ウィルス対策ソフト (Sophos / Trend Micro / Norton / Symantec / Kaspersky 等)
229
+ # - 企業の SSL インスペクション proxy (ZScaler / Cloudflare WARP for Teams 等)
230
+ # - VPN クライアントによるトラフィック検査
231
+ # - 広告ブロッカー (NextDNS / AdGuard / 1.1.1.1 等)
232
+ # - 親会社配布のセキュリティアプリ
233
+ #
234
+ # どれもユーザー操作なしには検出しにくく、ユーザー自身も気づいていないことが多い。
235
+ #
236
+ # 対処: ユーザーが既に OS で信頼している CA バンドルを node にも渡せば、curl と
237
+ # node の信頼ストアの乖離が解消する。`NODE_EXTRA_CA_CERTS` 環境変数を使えば
238
+ # node のバンドル CA に「追加で」信頼する証明書を渡せる (バンドル CA を置き換える
239
+ # わけではないので、通常環境への副作用はない)。
240
+ #
241
+ # 1. pre-flight check で node の TLS が通るかテスト
242
+ # 2. 失敗時のみ、OS の信頼ストアを PEM に書き出して NODE_EXTRA_CA_CERTS にセット
243
+ # 3. シェル profile (.zprofile / .bash_profile) にも追記して永続化
244
+ # 4. 再テスト → ダメなら明確な日本語エラーガイドで終了
245
+ # =============================================================================
246
+
247
+ # node 側 TLS で npm registry に到達できるかテスト。成功で 0、失敗で 1。
248
+ # stderr の最後の行を grep 用に echo するので、呼び出し側で原因種別を判定できる。
249
+ _test_node_tls() {
250
+ if ! present node; then
251
+ return 1
252
+ fi
253
+ # node -e で出る ERR_TLS_CERT_ALTNAME_INVALID 等の他種別エラーは別途扱う必要が
254
+ # あるため戻り値だけで判定。stdout/stderr は呼び出し側で破棄してよい。
255
+ node -e "require('https').get('https://registry.npmjs.org/', r => { process.exit(r.statusCode >= 400 ? 1 : 0); }).on('error', e => { console.error(e.code || e.message); process.exit(1); });" 2>&1
256
+ }
257
+
258
+ # OS の信頼ストア (system / login keychain or /etc/ssl/certs) を PEM に書き出す。
259
+ # 返り値: 成功なら 0 + ファイルパスを stdout に echo / 失敗なら 1。
260
+ _export_os_ca_bundle() {
261
+ local out_pem="$HOME/.hub-agent-ca.pem"
262
+ case "$(uname -s)" in
263
+ Darwin)
264
+ # System.keychain には企業配布 CA や手動追加 CA、SystemRootCertificates.keychain
265
+ # には Apple 配布の標準 root CA が入っている。両方をマージ。
266
+ # `-p` で PEM 形式、`-a` で全件出力。pemcat に近い操作。
267
+ {
268
+ security find-certificate -a -p /Library/Keychains/System.keychain 2>/dev/null || true
269
+ security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain 2>/dev/null || true
270
+ } > "$out_pem"
271
+ ;;
272
+ Linux)
273
+ # Debian/Ubuntu 系
274
+ if [[ -r /etc/ssl/certs/ca-certificates.crt ]]; then
275
+ cp /etc/ssl/certs/ca-certificates.crt "$out_pem"
276
+ # RHEL/CentOS/Fedora 系
277
+ elif [[ -r /etc/pki/tls/certs/ca-bundle.crt ]]; then
278
+ cp /etc/pki/tls/certs/ca-bundle.crt "$out_pem"
279
+ # SUSE 系
280
+ elif [[ -r /var/lib/ca-certificates/ca-bundle.pem ]]; then
281
+ cp /var/lib/ca-certificates/ca-bundle.pem "$out_pem"
282
+ else
283
+ return 1
284
+ fi
285
+ ;;
286
+ *)
287
+ return 1
288
+ ;;
289
+ esac
290
+ if [[ ! -s "$out_pem" ]]; then
291
+ rm -f "$out_pem"
292
+ return 1
293
+ fi
294
+ echo "$out_pem"
295
+ }
296
+
297
+ # シェル profile に `export NODE_EXTRA_CA_CERTS=...` を追記する。
298
+ # 既に同じパス指定の export 行があればスキップ (idempotent)。
299
+ _persist_node_extra_ca_certs() {
300
+ local ca_path="$1"
301
+ local snippet="export NODE_EXTRA_CA_CERTS=\"$ca_path\""
302
+ local marker="# >>> hub-agent: NODE_EXTRA_CA_CERTS (TLS fallback) >>>"
303
+ local end_marker="# <<< hub-agent: NODE_EXTRA_CA_CERTS <<<"
304
+ local target
305
+ for target in "$HOME/.zprofile" "$HOME/.bash_profile"; do
306
+ [[ -e "$target" ]] || touch "$target"
307
+ if grep -Fq "$snippet" "$target" 2>/dev/null; then
308
+ continue
309
+ fi
310
+ {
311
+ printf '\n%s\n' "$marker"
312
+ printf '%s\n' "$snippet"
313
+ printf '%s\n' "$end_marker"
314
+ } >> "$target"
315
+ color_ok "$target に NODE_EXTRA_CA_CERTS を追記"
316
+ done
317
+ }
318
+
319
+ # 「UNABLE_TO_GET_ISSUER_CERT_LOCALLY が出た時に何を確認すべきか」のガイド表示。
320
+ # フォールバックも効かなかった最終手段ケース用。
321
+ _print_tls_failure_guidance() {
322
+ # ヘッダーだけ printf で色付け。本文は cat <<EOF で複数行を読みやすく出す。
323
+ printf '\n\033[1;31m✗ node の TLS 検証が修復できませんでした。\033[0m\n'
324
+ cat <<EOF
325
+
326
+ curl は通るのに node だけ落ちる場合、お使いの Mac/PC に
327
+ HTTPS 通信を検査しているソフトが入っている可能性が高いです。
328
+ 典型例:
329
+ - ウィルス対策ソフト (Sophos / Norton / Trend Micro / Symantec 等)
330
+ - 親会社配布のセキュリティアプリ
331
+ - SSL インスペクション機能つきの VPN クライアント
332
+ - 広告ブロッカー (NextDNS / 1.1.1.1 for Families / AdGuard 等)
333
+
334
+ 以下のコマンドで「実際の証明書発行者」が見えます。
335
+ ここに表示される \`issuer\` が \`Let's Encrypt\` や \`DigiCert\` ではなく、
336
+ 特定のソフト名 (ZScaler / Cocorograph / 製品名 等) なら、それが原因です:
337
+
338
+ node -e 'const t=require("tls");const s=t.connect(443,"registry.npmjs.org",{servername:"registry.npmjs.org",rejectUnauthorized:false},()=>{let c=s.getPeerCertificate(true);while(c&&Object.keys(c).length){console.log("issuer:",JSON.stringify(c.issuer));if(!c.issuerCertificate||c.issuerCertificate===c)break;c=c.issuerCertificate;}s.end();});'
339
+
340
+ 暫定的に install を進めたい場合(自分の環境を信頼している前提):
341
+
342
+ npm install -g $PACKAGE_NAME --strict-ssl=false
343
+ npm install -g $CLAUDE_CODE_PACKAGE --strict-ssl=false
344
+ hub-agent enroll <token> --hub-url <hub-url>
345
+ hub-agent install-service
346
+
347
+ 詳細サポートは Hub の cockpit チャネルへ。
348
+
349
+ EOF
350
+ }
351
+
352
+ # pre-flight TLS 検査 + 自動 fallback の本体。
353
+ # main() から ensure_npm_user_prefix の直後で呼ぶ想定。
354
+ ensure_node_tls_works() {
355
+ # Linux の中には node を持たない経路 (本スクリプトより前に node が入る) もあるので
356
+ # node が無ければスキップ (後段の ensure_global_install で別エラーになる)。
357
+ present node || return 0
358
+
359
+ color_step "node の TLS 検証を pre-flight check"
360
+
361
+ # ステップ 0: brew link 直後はシェル command hash に古い node/npm パスが残る
362
+ # ことがあるので、念のためクリアしてから検査する。
363
+ hash -r 2>/dev/null || true
364
+
365
+ if _test_node_tls >/dev/null 2>&1; then
366
+ color_ok "node TLS 検証 OK (registry.npmjs.org に到達可能)"
367
+ return 0
368
+ fi
369
+
370
+ color_warn "node TLS 検証失敗 → OS 信頼ストアから CA をエクスポートして再試行"
371
+
372
+ local ca_pem
373
+ if ! ca_pem=$(_export_os_ca_bundle); then
374
+ color_err "OS の信頼ストア (macOS Keychain / Linux ca-certificates) からの CA エクスポートに失敗"
375
+ _print_tls_failure_guidance
376
+ exit 1
377
+ fi
378
+ color_ok "$ca_pem に OS 信頼ストアの CA を書き出し ($(wc -l < "$ca_pem" | tr -d ' ') 行)"
379
+
380
+ export NODE_EXTRA_CA_CERTS="$ca_pem"
381
+ _persist_node_extra_ca_certs "$ca_pem"
382
+
383
+ # 再テスト
384
+ if _test_node_tls >/dev/null 2>&1; then
385
+ color_ok "node TLS 検証 OK (NODE_EXTRA_CA_CERTS=$ca_pem 経由)"
386
+ return 0
387
+ fi
388
+
389
+ color_err "OS 信頼ストアを渡しても node TLS 検証が通りません"
390
+ _print_tls_failure_guidance
391
+ exit 1
392
+ }
393
+
207
394
  ensure_pkg() {
208
395
  local cmd="$1"
209
396
  local brew_pkg="$2"
@@ -225,23 +412,55 @@ ensure_pkg() {
225
412
  fi
226
413
  }
227
414
 
415
+ # macOS で brew 経由で Active LTS の node (NODE_DEFAULT_BREW_FORMULA) を導入/切替する。
416
+ # 既存の無印 node が link されていれば unlink してから新 formula を link --overwrite --force。
417
+ _install_node_lts_brew() {
418
+ local formula="$NODE_DEFAULT_BREW_FORMULA"
419
+ color_step "$formula (Active LTS) を install"
420
+ brew install "$formula"
421
+ # 既存無印 node が link されていれば外す(v26 current 等を退かす)
422
+ if brew list node >/dev/null 2>&1; then
423
+ color_step "既存 'node' formula を unlink ($formula を優先するため)"
424
+ brew unlink node 2>/dev/null || true
425
+ fi
426
+ color_step "$formula を link --overwrite --force"
427
+ brew link --overwrite --force "$formula"
428
+ hash -r 2>/dev/null || true
429
+ }
430
+
431
+ # Node.js のサポート範囲ポリシー判定。
432
+ # - 未インストール → LTS を install
433
+ # - [MIN, MAX] 範囲内 → 現状維持(ユーザーの環境を尊重)
434
+ # - 範囲未満 → LTS にアップグレード
435
+ # - 範囲超過(current 系等) → LTS にダウングレード
228
436
  ensure_node_version() {
229
437
  local v
230
438
  v=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)
231
439
  if [[ -z "$v" ]]; then
232
- ensure_pkg node node nodejs
233
- return
234
- fi
235
- if (( v < NODE_MIN_MAJOR )); then
236
- color_warn "node $v は古いです (>=${NODE_MIN_MAJOR} 必須)。brew で更新を試みます"
440
+ color_step "node が未インストール → $NODE_DEFAULT_BREW_FORMULA (Active LTS) を install"
237
441
  if [[ "$(uname)" == "Darwin" ]]; then
238
- brew upgrade node || brew install node
442
+ _install_node_lts_brew
239
443
  else
240
- color_err "Node ${NODE_MIN_MAJOR}+ を手動で install してください (nvm 推奨)"
241
- exit 1
444
+ ensure_pkg node node nodejs
242
445
  fi
446
+ return
447
+ fi
448
+
449
+ if (( v >= NODE_MIN_MAJOR && v <= NODE_MAX_MAJOR )); then
450
+ color_ok "node v$v は動作保証範囲 (${NODE_MIN_MAJOR} ≤ v ≤ ${NODE_MAX_MAJOR}) 内。現状維持"
451
+ return
452
+ fi
453
+
454
+ if (( v < NODE_MIN_MAJOR )); then
455
+ color_warn "node v$v は古い (>=${NODE_MIN_MAJOR} 必須) → $NODE_DEFAULT_BREW_FORMULA にアップグレード"
243
456
  else
244
- color_ok "node $v"
457
+ color_warn "node v$v は新しすぎ (current 系) → 安定動作のため $NODE_DEFAULT_BREW_FORMULA (LTS) にダウングレード"
458
+ fi
459
+ if [[ "$(uname)" == "Darwin" ]]; then
460
+ _install_node_lts_brew
461
+ else
462
+ color_err "Node $NODE_MIN_MAJOR〜$NODE_MAX_MAJOR の LTS を手動で install してください (nvm 推奨)"
463
+ exit 1
245
464
  fi
246
465
  }
247
466
 
@@ -329,6 +548,11 @@ main() {
329
548
  ensure_pkg tmux tmux tmux
330
549
  ensure_node_version
331
550
  ensure_npm_user_prefix
551
+ # ensure_node_version 直後だと brew link でシェルの command hash がズレている
552
+ # 場合があるため、prefix 設定後にまとめて TLS pre-flight check + 自動 fallback
553
+ # を行う。失敗時はガイダンス付きで exit するので、後段の npm install で TLS
554
+ # エラーを再度浴びる経路はカットされる。
555
+ ensure_node_tls_works
332
556
  ensure_global_install
333
557
  ensure_claude_code
334
558
  do_enroll