@cocorograph/hub-agent 0.6.72 → 0.6.74

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.6.72",
3
+ "version": "0.6.74",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,87 +1,109 @@
1
1
  # =============================================================================
2
- # hub-agent Windows ワンライナーインストーラ (WSL2 方式)
2
+ # hub-agent Windows one-liner installer (WSL2 method)
3
3
  #
4
- # Hub UI enrollment token + hub url を埋め込んだ PowerShell one-liner を表示し、
5
- # ユーザーが「管理者 PowerShell」にコピペ実行する。bash install.sh Windows 版。
4
+ # The Hub UI shows a PowerShell one-liner with the enrollment token + hub url
5
+ # embedded. The user pastes it into an ADMINISTRATOR PowerShell. This is the
6
+ # Windows counterpart of the bash install.sh.
6
7
  #
7
- # 設計書: D00000_hub-agent/docs/windows-installer-design.md
8
- # 方針判断: Hub ナレッジ `ナレッジ/Cockpit/Windows対応の設計判断` (id 7385)
8
+ # IMPORTANT: This script is intentionally ASCII-only. Windows PowerShell 5.1
9
+ # fetches the script with `irm` and runs it with `iex`; when the HTTP response
10
+ # has no charset, 5.1 decodes UTF-8 bytes using the legacy code page (e.g.
11
+ # CP932 on Japanese Windows), which turns any non-ASCII text into mojibake and
12
+ # can break parsing. Keeping everything ASCII avoids that entirely.
9
13
  #
10
- # 単独使用 (token なし):
14
+ # Design doc: D00000_hub-agent/docs/windows-installer-design.md
15
+ #
16
+ # Standalone (no token):
11
17
  # irm https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.ps1 | iex
12
18
  #
13
- # Hub UI が生成する形 (token/url 埋め込み):
19
+ # Form generated by the Hub UI (token/url embedded):
14
20
  # $env:HUB_AGENT_TOKEN="<tok>"; $env:HUB_AGENT_URL="https://api.hub.cocorograph.com"; `
15
21
  # irm https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.ps1 | iex
16
22
  #
17
- # 内容:
18
- # フェーズ1: WSL2 導入 (管理者必須・再起動またぎを RunOnce で吸収)
19
- # 再起動後 : Ubuntu 初回ユーザーを無人作成 (cloud-init root install フォールバック)
20
- # フェーズ2: WSL の中で既存 install.sh を呼ぶ (tmux/node/hub-agent/claude/enroll)
21
- # 常駐化 : Task Scheduler のログオンタスクで distro を起こす (中の systemd が hub-agent を起動)
23
+ # Flow:
24
+ # Phase 1: install WSL2 (admin required; reboot bridged via RunOnce)
25
+ # After reboot: create the first Ubuntu user unattended
26
+ # (cloud-init first, root-install + useradd as fallback)
27
+ # Phase 2: run the existing install.sh inside WSL
28
+ # (tmux/node/hub-agent/claude/enroll/install-service)
29
+ # Autostart: a Task Scheduler logon task wakes the distro so its systemd
30
+ # brings hub-agent up.
22
31
  # =============================================================================
23
32
 
24
33
  #Requires -Version 5.1
25
34
  $ErrorActionPreference = "Stop"
26
35
 
27
- # --- 定数 -------------------------------------------------------------------
28
- # distro LTS 固定 (将来の非互換回避)。launcher 実行ファイル名も distro に対応して固定。
29
- $DISTRO = "Ubuntu-24.04"
30
- $LAUNCHER = "ubuntu2404.exe"
31
- $INSTALL_SH = "https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.sh"
32
- $PS1_URL = "https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.ps1"
33
- $RUNONCE_NAME = "HubAgentSetup"
34
- $TASK_NAME = "HubAgentWSLBoot"
35
- $DEFAULT_HUB = "https://api.hub.cocorograph.com"
36
-
37
- # --- env からパラメータ取得 -------------------------------------------------
38
- $Token = $env:HUB_AGENT_TOKEN
39
- $HubUrl = if ($env:HUB_AGENT_URL) { $env:HUB_AGENT_URL } else { $DEFAULT_HUB }
40
- $IsResume = ($env:HUB_AGENT_RESUME -eq "1") # 再起動後の RunOnce 自動再開フラグ
41
-
42
- # --- カラーログ -------------------------------------------------------------
36
+ # --- constants --------------------------------------------------------------
37
+ # Preferred distro is the LTS. We try this first, then a couple of fallbacks
38
+ # (older Windows 10 WSL catalogs may not know "Ubuntu-24.04").
39
+ $PREFERRED_DISTRO = "Ubuntu-24.04"
40
+ $DISTRO_CANDIDATES = @("Ubuntu-24.04", "Ubuntu")
41
+ $INSTALL_SH = "https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.sh"
42
+ $PS1_URL = "https://unpkg.com/@cocorograph/hub-agent@latest/scripts/install.ps1"
43
+ $RUNONCE_NAME = "HubAgentSetup"
44
+ $TASK_NAME = "HubAgentWSLBoot"
45
+ $DEFAULT_HUB = "https://api.hub.cocorograph.com"
46
+
47
+ # --- params from env --------------------------------------------------------
48
+ $Token = $env:HUB_AGENT_TOKEN
49
+ $HubUrl = if ($env:HUB_AGENT_URL) { $env:HUB_AGENT_URL } else { $DEFAULT_HUB }
50
+ $IsResume = ($env:HUB_AGENT_RESUME -eq "1") # set by RunOnce after reboot
51
+
52
+ # --- colored logging (ASCII only) ------------------------------------------
43
53
  function Write-Step($m) { Write-Host "==> $m" -ForegroundColor Cyan }
44
54
  function Write-Ok($m) { Write-Host "[OK] $m" -ForegroundColor Green }
45
55
  function Write-Warn($m) { Write-Host "[!] $m" -ForegroundColor Yellow }
46
56
  function Write-Err($m) { Write-Host "[x] $m" -ForegroundColor Red }
47
57
 
48
- # --- ヘルパー ---------------------------------------------------------------
58
+ # --- helpers ----------------------------------------------------------------
49
59
  function Test-IsAdmin {
50
60
  $id = [Security.Principal.WindowsIdentity]::GetCurrent()
51
61
  $pr = New-Object Security.Principal.WindowsPrincipal($id)
52
62
  return $pr.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
53
63
  }
54
64
 
55
- # wsl -l -q UTF-16LE + NUL 混じりで出すため、サニタイズして配列化する。
65
+ # `wsl -l -q` prints UTF-16LE with stray NULs; sanitize and split to an array.
56
66
  function Get-WslDistros {
57
67
  try {
58
68
  $raw = (wsl.exe -l -q) 2>$null
59
69
  if (-not $raw) { return @() }
60
- return ($raw -replace "`0", "") -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
70
+ return ($raw -replace "`0", "") -split "`r?`n" |
71
+ ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
61
72
  } catch { return @() }
62
73
  }
63
74
 
64
- function Test-DistroExists { return (Get-WslDistros) -contains $DISTRO }
75
+ # Return the name of an installed Ubuntu distro (prefer 24.04), or $null.
76
+ function Resolve-InstalledUbuntu {
77
+ $installed = Get-WslDistros
78
+ foreach ($cand in $DISTRO_CANDIDATES) {
79
+ if ($installed -contains $cand) { return $cand }
80
+ }
81
+ # any distro whose name starts with "Ubuntu"
82
+ $u = $installed | Where-Object { $_ -like "Ubuntu*" } | Select-Object -First 1
83
+ if ($u) { return $u }
84
+ return $null
85
+ }
65
86
 
66
- # WSL 機能そのもの (カーネル + プラットフォーム) が使える状態か。
87
+ # Is the WSL engine (kernel + platform) usable at all?
67
88
  function Test-WslReady {
68
89
  try { wsl.exe --status *> $null; return ($LASTEXITCODE -eq 0) } catch { return $false }
69
90
  }
70
91
 
71
- # distro 内に対象ユーザーが存在するか (無人作成の冪等チェック)
72
- function Test-WslUserReady($user) {
73
- if (-not $user) { return $false }
74
- try { wsl.exe -d $DISTRO -u root -- id $user *> $null; return ($LASTEXITCODE -eq 0) } catch { return $false }
92
+ # Does user exist inside the given distro? (idempotency check)
93
+ function Test-WslUserReady($distro, $user) {
94
+ if (-not $distro -or -not $user) { return $false }
95
+ try { wsl.exe -d $distro -u root -- id $user *> $null; return ($LASTEXITCODE -eq 0) }
96
+ catch { return $false }
75
97
  }
76
98
 
77
- # Windows ユーザー名を Linux 命名規則にサニタイズ ([a-z_][a-z0-9_-]*)
99
+ # Sanitize Windows username to a valid Linux name ([a-z_][a-z0-9_-]*).
78
100
  function Get-LinuxUserName {
79
101
  $u = ($env:USERNAME).ToLower() -replace '[^a-z0-9_-]', ''
80
102
  if ($u -eq '' -or $u -match '^[0-9]') { $u = "hub$u" }
81
103
  return $u
82
104
  }
83
105
 
84
- # RunOnce に「再起動後に自身を RESUME=1 で再実行」を仕込む。token/url を引き継ぐ。
106
+ # Register "re-run myself with RESUME=1 after reboot", carrying token/url.
85
107
  function Set-ResumeOnReboot {
86
108
  $cmd = "powershell -NoProfile -ExecutionPolicy Bypass -Command " +
87
109
  "`"`$env:HUB_AGENT_TOKEN='$Token'; `$env:HUB_AGENT_URL='$HubUrl'; " +
@@ -90,21 +112,20 @@ function Set-ResumeOnReboot {
90
112
  -Name $RUNONCE_NAME -Value $cmd
91
113
  }
92
114
 
93
- # 再起動はユーザーに委ねる (自動実行はデータ保全リスク)。RunOnce 登録済なので「あとで」でも自動再開。
94
- function Prompt-RebootDecision {
115
+ # Leave the reboot decision to the user (auto-reboot risks unsaved work).
116
+ # RunOnce is already registered, so "later" still auto-resumes on next boot.
117
+ function Invoke-RebootPrompt {
95
118
  Write-Host ""
96
- Write-Step "WSL2 を導入しました。続行には Windows の再起動が必要です。"
97
- Write-Host " 再起動後、セットアップは自動で続行します (手動操作は不要)" -ForegroundColor Cyan
98
- $ans = Read-Host "今すぐ再起動しますか? [Y=今すぐ / N=あとで自分で再起動]"
99
- if ($ans -match '^[Yy]') {
100
- Restart-Computer
101
- } else {
102
- Write-Warn "あとで Windows を再起動すると、セットアップが自動で続きます。"
103
- }
119
+ Write-Step "WSL2 is installed. A Windows reboot is required to continue."
120
+ Write-Host " After reboot, setup resumes automatically (no manual steps)." -ForegroundColor Cyan
121
+ $ans = Read-Host "Reboot now? [Y = reboot now / N = I'll reboot later]"
122
+ if ($ans -match '^[Yy]') { Restart-Computer }
123
+ else { Write-Warn "Reboot Windows later and setup will continue automatically." }
104
124
  }
105
125
 
106
- # cloud-init user-data を「初回起動前」に配置する。
107
- # %USERPROFILE%\.cloud-init\<DistroName>.user-data に置くと初回起動で OOBE 無しに反映される。
126
+ # Place cloud-init user-data BEFORE first boot. WSL applies it on first launch,
127
+ # skipping the interactive OOBE. The file name must match the registered distro
128
+ # name, so we write one file per candidate name to be safe.
108
129
  function Write-CloudInit($user) {
109
130
  $ciDir = Join-Path $env:USERPROFILE ".cloud-init"
110
131
  New-Item -ItemType Directory -Force -Path $ciDir | Out-Null
@@ -124,35 +145,75 @@ write_files:
124
145
  [boot]
125
146
  systemd=true
126
147
  "@
127
- # WSL/cloud-init LF + UTF-8(BOM なし) を期待するため改行を正規化して書く。
128
- $yaml = $yaml -replace "`r`n", "`n"
129
- $path = Join-Path $ciDir "$DISTRO.user-data"
130
- [System.IO.File]::WriteAllText($path, $yaml, (New-Object System.Text.UTF8Encoding($false)))
131
- Write-Ok "cloud-init user-data を配置: $path"
148
+ $yaml = $yaml -replace "`r`n", "`n" # cloud-init wants LF + UTF-8 (no BOM)
149
+ $enc = New-Object System.Text.UTF8Encoding($false)
150
+ foreach ($name in $DISTRO_CANDIDATES) {
151
+ $path = Join-Path $ciDir "$name.user-data"
152
+ [System.IO.File]::WriteAllText($path, $yaml, $enc)
153
+ }
154
+ Write-Ok "cloud-init user-data placed in $ciDir"
132
155
  }
133
156
 
134
- # 方式A: cloud-init が完了するまで待つ。成功で $true。
135
- function Invoke-CloudInitProvision($user) {
136
- Write-Step "Ubuntu 初回ユーザーを無人作成 (cloud-init)"
157
+ # Update the WSL engine + online distro catalog. On Windows 10 the inbox WSL
158
+ # catalog is often stale and does not know Ubuntu-24.04; `wsl --update` pulls
159
+ # the Store WSL which refreshes the catalog. Failures are non-fatal (warn).
160
+ function Update-WslEngine {
161
+ Write-Step "Updating WSL engine (wsl --update)"
137
162
  try {
138
- # 初回起動をトリガー。cloud-init user-data を適用する。
139
- wsl.exe -d $DISTRO -u root -- cloud-init status --wait *> $null
140
- } catch {
141
- Write-Warn "cloud-init status --wait に失敗 (cloud-init 非対応の可能性)"
142
- return $false
143
- }
144
- return (Test-WslUserReady $user)
163
+ wsl.exe --update *> $null
164
+ if ($LASTEXITCODE -ne 0) { Write-Warn "wsl --update returned $LASTEXITCODE (continuing)" }
165
+ else { Write-Ok "WSL engine updated" }
166
+ } catch { Write-Warn "wsl --update failed (continuing)" }
145
167
  }
146
168
 
147
- # 方式B (フォールバック): launcher install --root useradd で無人作成。
148
- function Invoke-RootInstallProvision($user) {
149
- Write-Step "Ubuntu 初回ユーザーを無人作成 (root install フォールバック)"
150
- try {
151
- # OOBE 無しで root のみ初期化。既に登録済みなら無害。
152
- & $LAUNCHER install --root *> $null
153
- } catch {
154
- Write-Warn "$LAUNCHER install --root に失敗 (続行して useradd を試行)"
169
+ # Install a distro without launching it. Tries candidate names and a no-flag
170
+ # fallback (older WSL may not support --no-launch). Returns the installed name
171
+ # or throws with actionable guidance.
172
+ function Install-Distro {
173
+ $existing = Resolve-InstalledUbuntu
174
+ if ($existing) { Write-Ok "Ubuntu distro already installed: $existing"; return $existing }
175
+
176
+ Update-WslEngine
177
+
178
+ foreach ($name in $DISTRO_CANDIDATES) {
179
+ Write-Step "Installing distro: $name"
180
+ # 1) modern: --no-launch
181
+ wsl.exe --install -d $name --no-launch *> $null
182
+ if ($LASTEXITCODE -eq 0 -and (Get-WslDistros) -contains $name) { return $name }
183
+ # 2) older WSL may reject --no-launch; retry without it (still no OOBE
184
+ # because cloud-init is already staged).
185
+ wsl.exe --install -d $name *> $null
186
+ if ($LASTEXITCODE -eq 0 -and (Get-WslDistros) -contains $name) { return $name }
187
+ Write-Warn "Could not install '$name', trying next candidate..."
155
188
  }
189
+
190
+ # If we get here every candidate failed.
191
+ Write-Err "Failed to install a WSL Ubuntu distro automatically."
192
+ Write-Host ""
193
+ Write-Host "Please run these in an ADMINISTRATOR PowerShell, then re-run this installer:" -ForegroundColor Yellow
194
+ Write-Host " wsl --update" -ForegroundColor Yellow
195
+ Write-Host " wsl --list --online # confirm an Ubuntu name is listed" -ForegroundColor Yellow
196
+ Write-Host " wsl --install -d Ubuntu-24.04 # or the Ubuntu name shown above" -ForegroundColor Yellow
197
+ Write-Host ""
198
+ Write-Host "Note: Windows 10 needs version 21H2+ and an up-to-date WSL." -ForegroundColor Yellow
199
+ throw "distro install failed"
200
+ }
201
+
202
+ # cloud-init path: wait for completion, return $true if the user got created.
203
+ function Invoke-CloudInitProvision($distro, $user) {
204
+ Write-Step "Creating first Ubuntu user unattended (cloud-init)"
205
+ try { wsl.exe -d $distro -u root -- cloud-init status --wait *> $null }
206
+ catch { Write-Warn "cloud-init status --wait failed (cloud-init may be unsupported)"; return $false }
207
+ return (Test-WslUserReady $distro $user)
208
+ }
209
+
210
+ # Fallback: launcher `install --root` then useradd. Launcher exe name depends
211
+ # on the distro (ubuntu2404.exe etc.); resolve it from the distro name.
212
+ function Invoke-RootInstallProvision($distro, $user) {
213
+ Write-Step "Creating first Ubuntu user unattended (root-install fallback)"
214
+ $launcher = ($distro.ToLower() -replace '[^a-z0-9]', '') + ".exe" # Ubuntu-24.04 -> ubuntu2404.exe
215
+ try { & $launcher install --root *> $null }
216
+ catch { Write-Warn "$launcher install --root failed (continuing to useradd)" }
156
217
  $script = @"
157
218
  set -e
158
219
  useradd -m -s /bin/bash -G sudo '$user' 2>/dev/null || true
@@ -162,99 +223,103 @@ chmod 0440 /etc/sudoers.d/90-hub-$user
162
223
  printf '[user]\ndefault=$user\n[boot]\nsystemd=true\n' > /etc/wsl.conf
163
224
  "@
164
225
  $script = $script -replace "`r`n", "`n"
165
- wsl.exe -d $DISTRO -u root -- bash -lc $script
226
+ wsl.exe -d $distro -u root -- bash -lc $script
166
227
  wsl.exe --shutdown
167
- return (Test-WslUserReady $user)
228
+ return (Test-WslUserReady $distro $user)
168
229
  }
169
230
 
170
- # distro 内の /etc/wsl.conf に systemd=true が無ければ追記 (冪等)
171
- function Ensure-Systemd {
172
- $has = (wsl.exe -d $DISTRO -u root -- bash -lc "grep -q 'systemd=true' /etc/wsl.conf 2>/dev/null && echo yes || echo no")
231
+ # Ensure systemd=true in the distro's /etc/wsl.conf (idempotent).
232
+ function Set-Systemd($distro) {
233
+ $has = (wsl.exe -d $distro -u root -- bash -lc "grep -q 'systemd=true' /etc/wsl.conf 2>/dev/null && echo yes || echo no")
173
234
  if (($has -replace "`0","").Trim() -ne "yes") {
174
- wsl.exe -d $DISTRO -u root -- bash -lc "printf '[boot]\nsystemd=true\n' >> /etc/wsl.conf"
235
+ wsl.exe -d $distro -u root -- bash -lc "printf '[boot]\nsystemd=true\n' >> /etc/wsl.conf"
175
236
  wsl.exe --shutdown
176
237
  }
177
238
  }
178
239
 
179
- # Task Scheduler に「ログオン時に distro を起こす」タスクを登録 (Mac に無い新ピース)
180
- function Register-BootTask($user) {
181
- Write-Step "Windows 起動時の常駐起動を登録 (Task Scheduler)"
182
- $action = New-ScheduledTaskAction -Execute "wsl.exe" -Argument "-d $DISTRO -u $user -- /bin/true"
240
+ # Register a logon task that wakes the distro (its systemd starts hub-agent).
241
+ function Register-BootTask($distro, $user) {
242
+ Write-Step "Registering autostart on logon (Task Scheduler)"
243
+ $action = New-ScheduledTaskAction -Execute "wsl.exe" -Argument "-d $distro -u $user -- /bin/true"
183
244
  $trigger = New-ScheduledTaskTrigger -AtLogOn
184
245
  $set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
185
246
  Register-ScheduledTask -TaskName $TASK_NAME -Action $action -Trigger $trigger -Settings $set `
186
247
  -RunLevel Limited -Force | Out-Null
187
- Write-Ok "ログオンタスク '$TASK_NAME' を登録しました"
248
+ Write-Ok "Logon task '$TASK_NAME' registered"
188
249
  }
189
250
 
190
- # WSL の中で既存 install.sh を呼ぶ (tmux/node/hub-agent/claude/enroll/install-service)。
191
- function Invoke-WslInstaller($user) {
192
- Write-Step "WSL 内で hub-agent をセットアップ (既存 install.sh)"
251
+ # Run the existing install.sh inside WSL.
252
+ function Invoke-WslInstaller($distro, $user) {
253
+ Write-Step "Setting up hub-agent inside WSL (existing install.sh)"
193
254
  $tok = if ($Token) { $Token } else { "" }
194
255
  $inner = "curl -fsSL '$INSTALL_SH' | HUB_AGENT_TOKEN='$tok' HUB_AGENT_URL='$HubUrl' bash"
195
- wsl.exe -d $DISTRO -u $user -- bash -lc $inner
196
- if ($LASTEXITCODE -ne 0) { throw "WSL 内 install.sh が失敗しました (exit $LASTEXITCODE)" }
256
+ wsl.exe -d $distro -u $user -- bash -lc $inner
257
+ if ($LASTEXITCODE -ne 0) { throw "install.sh inside WSL failed (exit $LASTEXITCODE)" }
197
258
  }
198
259
 
199
260
  # =============================================================================
200
- # メイン
261
+ # main
201
262
  # =============================================================================
202
263
  function Main {
203
- Write-Step "hub-agent Windows セットアップを開始 (WSL2 方式)"
264
+ Write-Step "Starting hub-agent Windows setup (WSL2 method)"
204
265
 
205
266
  if (-not (Test-IsAdmin)) {
206
- Write-Err "管理者権限が必要です。PowerShell を『管理者として実行』で開き直してください。"
267
+ Write-Err "Administrator privileges are required. Reopen PowerShell as Administrator."
207
268
  exit 1
208
269
  }
209
270
 
210
271
  $user = Get-LinuxUserName
211
272
 
212
- # --- フェーズ1: WSL2 / distro 導入 (未導入時のみ。再起動をまたぐ) ---
213
- if (-not $IsResume -and (-not (Test-WslReady) -or -not (Test-DistroExists))) {
214
- Write-Step "WSL2 + $DISTRO を導入します"
215
- Set-ResumeOnReboot # 再起動後に自動再開する仕掛けを先に仕込む
216
- Write-CloudInit $user # 初回起動前に cloud-init を配置 (方式A の前提)
217
- wsl.exe --install -d $DISTRO --no-launch
218
- Prompt-RebootDecision # 再起動は Y/N でユーザーに委ねる
273
+ # --- Phase 1: install WSL2 + distro (only when none present; bridges reboot) ---
274
+ if (-not $IsResume -and -not (Resolve-InstalledUbuntu)) {
275
+ Write-Step "Installing WSL2 + Ubuntu"
276
+ Set-ResumeOnReboot # arm auto-resume first
277
+ Write-CloudInit $user # stage cloud-init before first boot
278
+ $installed = Install-Distro # robust: update + candidate names + fallbacks
279
+ Write-Ok "Distro installed: $installed"
280
+ Invoke-RebootPrompt # reboot is the user's choice
219
281
  return
220
282
  }
221
283
 
222
- # --- 再起動後 or 既に WSL 導入済み: ユーザー無人作成 ---
223
- if (-not (Test-DistroExists)) {
224
- # RESUME なのに distro が無い = wsl --install が再起動後に展開中のことがある。
225
- Write-Warn "$DISTRO がまだ見つかりません。展開を待って初回起動を試みます..."
284
+ # --- after reboot, or WSL already present: resolve the real distro name ---
285
+ $distro = Resolve-InstalledUbuntu
286
+ if (-not $distro) {
287
+ # RESUME but distro not visible yet: the deployment may still be unpacking.
288
+ Write-Warn "Ubuntu distro not found yet; attempting install..."
226
289
  Write-CloudInit $user
290
+ $distro = Install-Distro
227
291
  }
292
+ Write-Ok "Using distro: $distro"
228
293
 
229
- if (-not (Test-WslUserReady $user)) {
230
- # 方式A (cloud-init) 失敗時 方式B (root install) へ自動フォールバック。
231
- if (-not (Test-Path (Join-Path $env:USERPROFILE ".cloud-init\$DISTRO.user-data"))) {
294
+ # --- create the first Ubuntu user unattended ---
295
+ if (-not (Test-WslUserReady $distro $user)) {
296
+ if (-not (Test-Path (Join-Path $env:USERPROFILE ".cloud-init\$distro.user-data"))) {
232
297
  Write-CloudInit $user
233
298
  }
234
- $ok = Invoke-CloudInitProvision $user
299
+ $ok = Invoke-CloudInitProvision $distro $user
235
300
  if (-not $ok) {
236
- Write-Warn "cloud-init での作成を確認できませんでした。root install 方式に切り替えます。"
237
- $ok = Invoke-RootInstallProvision $user
301
+ Write-Warn "cloud-init did not create the user; switching to root-install method."
302
+ $ok = Invoke-RootInstallProvision $distro $user
238
303
  }
239
- if (-not $ok) { throw "Ubuntu ユーザー '$user' の無人作成に失敗しました" }
240
- Write-Ok "Ubuntu ユーザー '$user' を作成しました (パスワードなし / NOPASSWD sudo)"
304
+ if (-not $ok) { throw "Failed to create Ubuntu user '$user' unattended" }
305
+ Write-Ok "Created Ubuntu user '$user' (no password / NOPASSWD sudo)"
241
306
  } else {
242
- Write-Ok "Ubuntu ユーザー '$user' は既に存在します"
307
+ Write-Ok "Ubuntu user '$user' already exists"
243
308
  }
244
309
 
245
- Ensure-Systemd
310
+ Set-Systemd $distro
246
311
 
247
- # --- フェーズ2: WSL 内ワンライナー ---
248
- Invoke-WslInstaller $user
312
+ # --- Phase 2: run install.sh inside WSL ---
313
+ Invoke-WslInstaller $distro $user
249
314
 
250
- # --- 常駐化 ---
251
- Register-BootTask $user
315
+ # --- autostart ---
316
+ Register-BootTask $distro $user
252
317
 
253
318
  Write-Host ""
254
- Write-Ok "セットアップ完了。Hub UI online 表示を確認してください"
319
+ Write-Ok "Setup complete. Check the online status in the Hub UI:"
255
320
  Write-Host " https://hub.cocorograph.com/user/cockpit/agents" -ForegroundColor Cyan
256
- Write-Warn "注意: 作業リポジトリは WSL (例: /home/$user/...) に置いてください。"
257
- Write-Warn " Windows ドライブ (/mnt/c/...) はファイル監視が不安定です。"
321
+ Write-Warn "Keep your working repos INSIDE WSL (e.g. /home/$user/...)."
322
+ Write-Warn "Windows drives (/mnt/c/...) have unreliable file watching."
258
323
  }
259
324
 
260
325
  Main
@@ -483,7 +483,10 @@ _install_node_lts_brew() {
483
483
  # - 範囲超過(current 系等) → LTS にダウングレード
484
484
  ensure_node_version() {
485
485
  local v
486
- v=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)
486
+ # node 皆無のクリーン環境では `node --version` exit 127。set -euo pipefail
487
+ # では pipefail がこれを拾ってスクリプトごと死ぬため、`|| v=""` で必ず吸収する
488
+ # (macOS テスト機には既に node があり露見しなかった潜在バグ。WSL クリーン Ubuntu で発覚)。
489
+ v=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1) || v=""
487
490
  if [[ -z "$v" ]]; then
488
491
  color_step "node が未インストール → $NODE_DEFAULT_BREW_FORMULA (Active LTS) を install"
489
492
  if [[ "$(uname)" == "Darwin" ]]; then