@beeos-ai/cli 1.1.0 → 1.1.1
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 +1 -1
- package/scripts/install.Tests.ps1 +58 -1
- package/scripts/install.ps1 +127 -12
- package/scripts/install.sh +177 -2
package/package.json
CHANGED
|
@@ -86,7 +86,12 @@ Describe "install.ps1 parse-time integrity" {
|
|
|
86
86
|
# function for Linux. Listed here so a future cleanup
|
|
87
87
|
# that drops the helper triggers a Pester failure
|
|
88
88
|
# before reaching customers.
|
|
89
|
-
"Test-VncServer"
|
|
89
|
+
"Test-VncServer",
|
|
90
|
+
# EACCES auto-recovery (Phase 3 of the install pipeline).
|
|
91
|
+
# install.sh has the parallel `recover_eacces_prefix`.
|
|
92
|
+
# Dropping this helper would silently regress the Phase 3
|
|
93
|
+
# self-healing path back to "hint + exit 3".
|
|
94
|
+
"Invoke-RecoverEaccesPrefix"
|
|
90
95
|
)
|
|
91
96
|
|
|
92
97
|
foreach ($name in $expected) {
|
|
@@ -167,6 +172,58 @@ Describe "install.ps1 stderr log file (P1-I)" {
|
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|
|
175
|
+
# ── EACCES auto-recovery (Phase 3) ───────────────────────────
|
|
176
|
+
#
|
|
177
|
+
# Static / structural guards for the Phase 3 self-healing path.
|
|
178
|
+
# Mirrors the install.sh design: when `npm install -g` fails with
|
|
179
|
+
# EACCES on the global node_modules, swap the npm prefix to a
|
|
180
|
+
# user-owned directory and retry exactly once. We deliberately do
|
|
181
|
+
# NOT mock `npm` / `[Environment]::SetEnvironmentVariable` here —
|
|
182
|
+
# Pester mocks for static .NET methods are fragile, and the rest of
|
|
183
|
+
# this test file uses structural / regex assertions for the same
|
|
184
|
+
# reason. Behaviour is exercised end-to-end via the staging EACCES
|
|
185
|
+
# soak (see `.cursor/skills/deploy-staging/install-verify.md`).
|
|
186
|
+
|
|
187
|
+
Describe "install.ps1 EACCES auto-recovery" {
|
|
188
|
+
It "writes to User-scope PATH (never Machine, to avoid UAC)" {
|
|
189
|
+
$content = Get-Content -Raw -Path $script:ScriptPath
|
|
190
|
+
# The recovery MUST persist on User scope; Machine-scope writes
|
|
191
|
+
# would silently re-prompt UAC and undo the whole point of
|
|
192
|
+
# falling back to a user-owned prefix.
|
|
193
|
+
$content | Should -Match 'SetEnvironmentVariable\(\s*"PATH"\s*,[^,]+,\s*"User"\s*\)'
|
|
194
|
+
$content | Should -Not -Match 'SetEnvironmentVariable\(\s*"PATH"\s*,[^,]+,\s*"Machine"\s*\)'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
It "honours BEEOS_NO_NPM_PREFIX_FIX=1 as an opt-out" {
|
|
198
|
+
$content = Get-Content -Raw -Path $script:ScriptPath
|
|
199
|
+
$content | Should -Match "BEEOS_NO_NPM_PREFIX_FIX"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
It "matches the EACCES fingerprint in the npm log" {
|
|
203
|
+
$content = Get-Content -Raw -Path $script:ScriptPath
|
|
204
|
+
# Same fingerprint string as install.sh's `grep -qE` — keeps
|
|
205
|
+
# the two scripts' classifier behaviour aligned. If you tighten
|
|
206
|
+
# the regex on one side, tighten it here too (and on
|
|
207
|
+
# install.sh) so a real EACCES on either platform still
|
|
208
|
+
# triggers recovery.
|
|
209
|
+
$content | Should -Match 'EACCES.*permission denied.*node_modules'
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
It "emits install.bootstrap.npm_recovered on retry success" {
|
|
213
|
+
$content = Get-Content -Raw -Path $script:ScriptPath
|
|
214
|
+
$content | Should -Match 'install\.bootstrap\.npm_recovered'
|
|
215
|
+
$content | Should -Match 'eacces_prefix'
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
It "still falls through to exit 3 when recovery is skipped or fails" {
|
|
219
|
+
$content = Get-Content -Raw -Path $script:ScriptPath
|
|
220
|
+
# Regression guard: the original hint + exit 3 path MUST stay
|
|
221
|
+
# reachable for non-EACCES failure modes (ETIMEDOUT, EBADENGINE,
|
|
222
|
+
# ENOSPC ...) and for the case where recovery itself bails.
|
|
223
|
+
$content | Should -Match 'exit 3'
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
170
227
|
# ── Dry-run hook ─────────────────────────────────────────────
|
|
171
228
|
|
|
172
229
|
Describe "install.ps1 dry-run hook" {
|
package/scripts/install.ps1
CHANGED
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
install-link refactor).
|
|
32
32
|
$env:BEEOS_USE_NPX = "1" Power-user throwaway install (beeos
|
|
33
33
|
won't persist on PATH).
|
|
34
|
+
$env:BEEOS_NO_NPM_PREFIX_FIX="1" Disable Phase 3 EACCES auto-recovery.
|
|
35
|
+
Recovery (default ON) detects
|
|
36
|
+
"EACCES on lib/node_modules" in the
|
|
37
|
+
npm log, switches the npm prefix
|
|
38
|
+
to %APPDATA%\npm, prepends it on
|
|
39
|
+
User-scope PATH (NEVER Machine —
|
|
40
|
+
that would re-trigger UAC), and
|
|
41
|
+
retries `npm install -g` once.
|
|
42
|
+
Set to 1 to fall back to the legacy
|
|
43
|
+
"hint + exit 3" behaviour.
|
|
34
44
|
|
|
35
45
|
IMPORTANT (env inheritance):
|
|
36
46
|
$env:BEEOS_API_URL / BEEOS_AGENT_GATEWAY_URL / BEEOS_DASHBOARD_URL
|
|
@@ -431,6 +441,80 @@ function Show-InstallHints {
|
|
|
431
441
|
}
|
|
432
442
|
}
|
|
433
443
|
|
|
444
|
+
# ── EACCES auto-recovery (Phase 3) ────────────────────────────
|
|
445
|
+
#
|
|
446
|
+
# Mirror of `install.sh::recover_eacces_prefix`. Phase 2 (Node install)
|
|
447
|
+
# already self-heals via winget → choco; Phase 3 (`npm install -g`)
|
|
448
|
+
# historically just printed a hint and exited. EACCES on Windows is
|
|
449
|
+
# rarer than on macOS — the default npm prefix is already user-scoped
|
|
450
|
+
# (`%APPDATA%\npm`) — but it does happen when an Administrator-
|
|
451
|
+
# installed Node has flipped the prefix to `C:\Program Files\nodejs\
|
|
452
|
+
# node_modules`. The fix is symmetric to the POSIX path: switch the
|
|
453
|
+
# prefix back to a user-owned directory and persist on User-scope
|
|
454
|
+
# PATH. We DELIBERATELY do not touch Machine-scope PATH — that would
|
|
455
|
+
# require UAC and silently re-trigger the elevation prompt the user
|
|
456
|
+
# already declined when winget/choco asked.
|
|
457
|
+
#
|
|
458
|
+
# Escape hatch: `$env:BEEOS_NO_NPM_PREFIX_FIX = "1"` short-circuits
|
|
459
|
+
# this function so power users / strict-policy operators can opt out.
|
|
460
|
+
function Invoke-RecoverEaccesPrefix {
|
|
461
|
+
if ($env:BEEOS_NO_NPM_PREFIX_FIX -eq "1") {
|
|
462
|
+
Write-BeeInfo "BEEOS_NO_NPM_PREFIX_FIX=1 — skipping auto npm prefix switch."
|
|
463
|
+
return $false
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
$appData = $env:APPDATA
|
|
467
|
+
if (-not $appData -or $appData.Length -eq 0) {
|
|
468
|
+
Write-BeeWarn "APPDATA env not set — cannot pick a user prefix. Falling back to hint."
|
|
469
|
+
return $false
|
|
470
|
+
}
|
|
471
|
+
$prefix = Join-Path $appData "npm"
|
|
472
|
+
Write-BeeInfo "Switching npm global prefix to $prefix (user-owned)."
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
New-Item -ItemType Directory -Path $prefix -Force | Out-Null
|
|
476
|
+
} catch {
|
|
477
|
+
Write-BeeError "Could not create ${prefix}: $_"
|
|
478
|
+
return $false
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
& npm config set prefix "$prefix" 2>&1 | Tee-Object -FilePath $BeeosInstallLog -Append | Out-Null
|
|
483
|
+
if ($LASTEXITCODE -ne 0) {
|
|
484
|
+
Write-BeeError "npm config set prefix failed (exit $LASTEXITCODE)."
|
|
485
|
+
return $false
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
Write-BeeError "npm config set prefix threw: $_"
|
|
489
|
+
return $false
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
# Make $prefix usable for the upcoming retry IN this session.
|
|
493
|
+
# Persistence (next terminal) happens via SetEnvironmentVariable
|
|
494
|
+
# below.
|
|
495
|
+
$env:Path = "$prefix;" + $env:Path
|
|
496
|
+
|
|
497
|
+
# Persist to User-scope PATH so a new terminal sees it. Idempotent —
|
|
498
|
+
# re-running on an already-fixed host MUST NOT append a duplicate
|
|
499
|
+
# entry (the User PATH is global to the account; appending without
|
|
500
|
+
# a contains-check would grow it unboundedly across re-runs).
|
|
501
|
+
try {
|
|
502
|
+
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
|
|
503
|
+
if (-not $userPath) { $userPath = "" }
|
|
504
|
+
if (($userPath -split ";") -notcontains $prefix) {
|
|
505
|
+
$newUserPath = if ($userPath.Length -gt 0) { "$prefix;$userPath" } else { "$prefix" }
|
|
506
|
+
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
|
|
507
|
+
Write-BeeInfo "Persisted PATH to user environment. Set BEEOS_NO_NPM_PREFIX_FIX=1 to skip on re-runs."
|
|
508
|
+
} else {
|
|
509
|
+
Write-BeeInfo "User PATH already contains $prefix — leaving as-is."
|
|
510
|
+
}
|
|
511
|
+
} catch {
|
|
512
|
+
Write-BeeWarn "Could not persist user PATH: $_"
|
|
513
|
+
Write-BeeWarn " Add $prefix to PATH manually to keep beeos discoverable in new terminals."
|
|
514
|
+
}
|
|
515
|
+
return $true
|
|
516
|
+
}
|
|
517
|
+
|
|
434
518
|
# ── Run CLI ──────────────────────────────────────────────────
|
|
435
519
|
|
|
436
520
|
function Invoke-BeeosCli {
|
|
@@ -469,18 +553,49 @@ function Invoke-BeeosCli {
|
|
|
469
553
|
& npm install -g $CliPackage 2>&1 | Tee-Object -FilePath $BeeosInstallLog -Append
|
|
470
554
|
$npmExit = $LASTEXITCODE
|
|
471
555
|
if ($npmExit -ne 0) {
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
#
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
556
|
+
# ── EACCES auto-recovery ──────────────────────────────────
|
|
557
|
+
# Mirror of install.sh's failure-branch recovery. We grep the
|
|
558
|
+
# teed log for the EACCES fingerprint; if it matches AND the
|
|
559
|
+
# user hasn't disabled the auto-fix, we swap the npm prefix
|
|
560
|
+
# to %APPDATA%\npm + persist to User PATH and retry exactly
|
|
561
|
+
# once. Other npm error classes (ENOSPC / E401 / EBADENGINE
|
|
562
|
+
# / ...) fall through to the existing hint+exit path —
|
|
563
|
+
# rarer, and need user judgement we can't safely automate.
|
|
564
|
+
$recovered = $false
|
|
565
|
+
$logContent = if (Test-Path $BeeosInstallLog) {
|
|
566
|
+
Get-Content -Raw -Path $BeeosInstallLog
|
|
567
|
+
} else { "" }
|
|
568
|
+
if ($logContent -match 'EACCES.*permission denied.*node_modules') {
|
|
569
|
+
Write-BeeWarn "Detected EACCES on global node_modules — attempting auto-recovery."
|
|
570
|
+
if (Invoke-RecoverEaccesPrefix) {
|
|
571
|
+
Write-BeeInfo "Retrying npm install with user-owned prefix..."
|
|
572
|
+
& npm install -g $CliPackage 2>&1 | Tee-Object -FilePath $BeeosInstallLog -Append
|
|
573
|
+
if ($LASTEXITCODE -eq 0) {
|
|
574
|
+
Send-Telemetry -Event "install.bootstrap.npm_recovered" -ErrorCode "eacces_prefix"
|
|
575
|
+
$recovered = $true
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
# ── End recovery ──────────────────────────────────────────
|
|
580
|
+
|
|
581
|
+
if (-not $recovered) {
|
|
582
|
+
# P0-A of the install-link review: fail-after-bootstrap signal.
|
|
583
|
+
# The `install.bootstrap.cli_installed` event is only emitted
|
|
584
|
+
# AFTER npm reports success.
|
|
585
|
+
Send-Telemetry -Event "install.bootstrap.npm_fail" -ErrorCode "npm_install_cli_failed" -Success $false
|
|
586
|
+
Write-BeeError "npm install -g $CliPackage failed."
|
|
587
|
+
Write-BeeError " Full log: $BeeosInstallLog"
|
|
588
|
+
Write-BeeError ""
|
|
589
|
+
Write-BeeError "Common fixes:"
|
|
590
|
+
Write-BeeError " - EEXIST on beeos from another package:"
|
|
591
|
+
Write-BeeError " npm uninstall -g @beeos-ai/cli # then re-run installer"
|
|
592
|
+
Write-BeeError " - EACCES / permission error:"
|
|
593
|
+
Write-BeeError " auto-recovery already attempted; if it failed, manually"
|
|
594
|
+
Write-BeeError " run ``npm config set prefix `"`$env:APPDATA\npm`"`` and"
|
|
595
|
+
Write-BeeError " add `$env:APPDATA\npm to your User PATH, then re-run installer."
|
|
596
|
+
Write-BeeError " (Set `$env:BEEOS_NO_NPM_PREFIX_FIX = '1' to disable auto-recovery.)"
|
|
597
|
+
exit 3
|
|
598
|
+
}
|
|
484
599
|
}
|
|
485
600
|
# Device-agent suite is intentionally NOT installed here; `beeos
|
|
486
601
|
# device attach` will auto-install it on first use via
|
package/scripts/install.sh
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
# 3 `npm install -g @beeos-ai/cli` failed (CLI didn't reach PATH)
|
|
30
30
|
#
|
|
31
31
|
# Automation can branch on these — `1` is "fix your Node install",
|
|
32
|
-
# `2` is "wrong machine", `3` is "your npm prefix / proxy / disk
|
|
32
|
+
# `2` is "wrong machine", `3` is "your npm prefix / proxy / disk
|
|
33
|
+
# (after EACCES auto-recovery already attempted, see
|
|
34
|
+
# `BEEOS_NO_NPM_PREFIX_FIX` below)".
|
|
33
35
|
#
|
|
34
36
|
# ── Optional env vars ─────────────────────────────────────────────
|
|
35
37
|
# BEEOS_API_URL Public API base (this script uses it for
|
|
@@ -51,6 +53,22 @@
|
|
|
51
53
|
# install.ps1's same-named env var.
|
|
52
54
|
# BEEOS_USE_NPX=1 Power-user throwaway install (beeos won't
|
|
53
55
|
# persist on PATH).
|
|
56
|
+
# BEEOS_NO_NPM_PREFIX_FIX=1 Disable Phase 3 EACCES auto-recovery.
|
|
57
|
+
# Recovery (default ON) detects "EACCES on
|
|
58
|
+
# lib/node_modules" in the npm log, switches
|
|
59
|
+
# the npm global prefix to ~/.npm-global,
|
|
60
|
+
# persists $HOME/.npm-global/bin on PATH via
|
|
61
|
+
# the user's shell rc (zsh -> ~/.zshrc, bash
|
|
62
|
+
# -> ~/.bash_profile on macOS or ~/.bashrc
|
|
63
|
+
# on Linux), and retries `npm install -g`
|
|
64
|
+
# exactly once. The shell rc edit is wrapped
|
|
65
|
+
# in a sentinel block (`# >>> beeos
|
|
66
|
+
# npm-prefix >>>` ... `# <<< beeos
|
|
67
|
+
# npm-prefix <<<`) so it can be reverted by
|
|
68
|
+
# removing the block AND running
|
|
69
|
+
# `npm config delete prefix`. Set this var
|
|
70
|
+
# to 1 to fall back to the legacy
|
|
71
|
+
# "hint + exit 3" behaviour.
|
|
54
72
|
#
|
|
55
73
|
# IMPORTANT (env inheritance):
|
|
56
74
|
# BEEOS_API_URL / BEEOS_AGENT_GATEWAY_URL / BEEOS_DASHBOARD_URL must
|
|
@@ -507,6 +525,138 @@ test_vnc_server() {
|
|
|
507
525
|
echo ""
|
|
508
526
|
}
|
|
509
527
|
|
|
528
|
+
# ── EACCES auto-recovery (Phase 3) ────────────────────────────
|
|
529
|
+
#
|
|
530
|
+
# Phase 2 of the bootstrap pipeline (Node install) already has a
|
|
531
|
+
# multi-strategy auto-recovery loop in `try_install_node` (nvm → fnm
|
|
532
|
+
# → brew). Phase 3 (`npm install -g @beeos-ai/cli`) historically had
|
|
533
|
+
# none — it just printed a hint and exited 3. The single most common
|
|
534
|
+
# Phase 3 failure is EACCES on the system-owned global node_modules
|
|
535
|
+
# (typically `/usr/local/lib/node_modules` after a nodejs.org .pkg
|
|
536
|
+
# install or a sudo brew install). The two helpers below close that
|
|
537
|
+
# gap with the same shape Phase 2 uses: a small detect function +
|
|
538
|
+
# an idempotent recovery function. `run_cli` calls them only when
|
|
539
|
+
# the npm log carries the EACCES fingerprint; every other npm
|
|
540
|
+
# failure mode is rarer and needs user judgement we can't safely
|
|
541
|
+
# automate from a curl|bash one-liner.
|
|
542
|
+
|
|
543
|
+
# Detect the user's preferred shell init file so that an `export
|
|
544
|
+
# PATH=...` write survives a new terminal. We support zsh + bash
|
|
545
|
+
# (~95% of macOS / Linux users); fish / tcsh / nushell users get a
|
|
546
|
+
# printed hint instead of a silent edit to a file we don't fully
|
|
547
|
+
# understand. `$SHELL` is the login shell as recorded in /etc/passwd,
|
|
548
|
+
# NOT the currently-running interpreter — that's exactly what we want
|
|
549
|
+
# for "what does the user's NEXT terminal source?".
|
|
550
|
+
detect_shell_rc() {
|
|
551
|
+
local shell_name
|
|
552
|
+
shell_name="$(basename "${SHELL:-}")"
|
|
553
|
+
case "$shell_name" in
|
|
554
|
+
zsh)
|
|
555
|
+
# zsh's per-user interactive rc. macOS Terminal.app sources it
|
|
556
|
+
# for both login and non-login zsh sessions. We deliberately do
|
|
557
|
+
# NOT touch ~/.zprofile (login-only) — keeping all our writes
|
|
558
|
+
# in a single file makes "how do I revert?" a one-line answer.
|
|
559
|
+
printf '%s' "$HOME/.zshrc"
|
|
560
|
+
;;
|
|
561
|
+
bash)
|
|
562
|
+
# macOS Terminal.app launches each window as a login bash, so
|
|
563
|
+
# the canonical sourced file is ~/.bash_profile. Linux GUI
|
|
564
|
+
# terminals run interactive non-login bash and source ~/.bashrc.
|
|
565
|
+
if [[ "$OS_KIND" == "darwin" ]]; then
|
|
566
|
+
printf '%s' "$HOME/.bash_profile"
|
|
567
|
+
else
|
|
568
|
+
printf '%s' "$HOME/.bashrc"
|
|
569
|
+
fi
|
|
570
|
+
;;
|
|
571
|
+
*)
|
|
572
|
+
# Empty string ⇒ caller falls back to a manual-paste hint.
|
|
573
|
+
printf '%s' ""
|
|
574
|
+
;;
|
|
575
|
+
esac
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
# Switch the npm global prefix to a user-owned directory and persist
|
|
579
|
+
# the resulting PATH change to the user's shell rc. Used as the EACCES
|
|
580
|
+
# auto-recovery in `run_cli`. Idempotent — running on an already-
|
|
581
|
+
# fixed host is a no-op (the sentinel block check below prevents
|
|
582
|
+
# duplicate appends).
|
|
583
|
+
#
|
|
584
|
+
# Why this approach (and not the alternatives):
|
|
585
|
+
#
|
|
586
|
+
# - sudo retry: would leave /usr/local/lib/node_modules/@beeos-ai/cli
|
|
587
|
+
# owned by root, breaking the next `npm update -g` and creating
|
|
588
|
+
# ownership inconsistencies between the CLI binary and ~/.beeos
|
|
589
|
+
# (which the user owns). It's the kind of "fix" that buries the
|
|
590
|
+
# real problem deeper.
|
|
591
|
+
#
|
|
592
|
+
# - force-install nvm: intrusive for a user who already has a
|
|
593
|
+
# working Node toolchain, and risks breaking other tools that
|
|
594
|
+
# depend on the system Node path.
|
|
595
|
+
#
|
|
596
|
+
# - just print a hint: the original behaviour. Pushes the fix onto
|
|
597
|
+
# the user, who has to switch contexts mid-install. Most users
|
|
598
|
+
# give up here.
|
|
599
|
+
#
|
|
600
|
+
# The chosen path mirrors npm's own documentation:
|
|
601
|
+
# https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally
|
|
602
|
+
#
|
|
603
|
+
# Escape hatch: `BEEOS_NO_NPM_PREFIX_FIX=1` short-circuits this
|
|
604
|
+
# function so power users / strict-dotfiles operators can opt out.
|
|
605
|
+
recover_eacces_prefix() {
|
|
606
|
+
if [[ "${BEEOS_NO_NPM_PREFIX_FIX:-}" == "1" ]]; then
|
|
607
|
+
info "BEEOS_NO_NPM_PREFIX_FIX=1 — skipping auto npm prefix switch."
|
|
608
|
+
return 1
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
local prefix="$HOME/.npm-global"
|
|
612
|
+
info "Switching npm global prefix to ${prefix} (user-owned)."
|
|
613
|
+
|
|
614
|
+
if ! mkdir -p "$prefix" >> "$BEEOS_INSTALL_LOG" 2>&1; then
|
|
615
|
+
error "Could not create ${prefix} — falling back to hint."
|
|
616
|
+
return 1
|
|
617
|
+
fi
|
|
618
|
+
if ! npm config set prefix "$prefix" >> "$BEEOS_INSTALL_LOG" 2>&1; then
|
|
619
|
+
error "npm config set prefix failed — falling back to hint."
|
|
620
|
+
return 1
|
|
621
|
+
fi
|
|
622
|
+
|
|
623
|
+
# Make the new prefix usable for the upcoming retry IN this
|
|
624
|
+
# session. Persistence (next terminal) happens via shell rc below.
|
|
625
|
+
export PATH="$prefix/bin:$PATH"
|
|
626
|
+
|
|
627
|
+
local rc
|
|
628
|
+
rc="$(detect_shell_rc)"
|
|
629
|
+
if [[ -z "$rc" ]]; then
|
|
630
|
+
warn "Unsupported login shell ($(basename "${SHELL:-unknown}")) — could not"
|
|
631
|
+
warn " persist PATH automatically. Add this line to your shell init:"
|
|
632
|
+
warn " export PATH=\"$prefix/bin:\$PATH\""
|
|
633
|
+
return 0
|
|
634
|
+
fi
|
|
635
|
+
|
|
636
|
+
# Sentinel block — grep + sed can remove it cleanly. We check for
|
|
637
|
+
# the BEGIN marker so re-runs of the installer don't append the
|
|
638
|
+
# block twice (the file may have been edited by hand between runs).
|
|
639
|
+
local marker_begin="# >>> beeos npm-prefix >>>"
|
|
640
|
+
local marker_end="# <<< beeos npm-prefix <<<"
|
|
641
|
+
if [[ -f "$rc" ]] && grep -qF "$marker_begin" "$rc"; then
|
|
642
|
+
info "Sentinel block already present in ${rc} — leaving as-is."
|
|
643
|
+
return 0
|
|
644
|
+
fi
|
|
645
|
+
|
|
646
|
+
# `>>` never clobbers existing content. If the rc doesn't exist yet
|
|
647
|
+
# (fresh user account), the redirect creates it with 0644 perms.
|
|
648
|
+
{
|
|
649
|
+
printf '\n%s\n' "$marker_begin"
|
|
650
|
+
printf '# Added by https://beeos.ai/install on %s\n' "$(date -u +%FT%TZ)"
|
|
651
|
+
printf '# Reason: system npm prefix was not writable (EACCES).\n'
|
|
652
|
+
printf '# To revert: delete this block AND run `npm config delete prefix`.\n'
|
|
653
|
+
printf 'export PATH="%s/bin:$PATH"\n' "$prefix"
|
|
654
|
+
printf '%s\n' "$marker_end"
|
|
655
|
+
} >> "$rc"
|
|
656
|
+
info "Persisted PATH to ${rc}. Set BEEOS_NO_NPM_PREFIX_FIX=1 to skip on re-runs."
|
|
657
|
+
return 0
|
|
658
|
+
}
|
|
659
|
+
|
|
510
660
|
# ── Main ─────────────────────────────────────────────────────
|
|
511
661
|
|
|
512
662
|
run_cli() {
|
|
@@ -553,6 +703,28 @@ run_cli() {
|
|
|
553
703
|
# the time they go to copy-paste. `set -o pipefail` (file header)
|
|
554
704
|
# ensures the pipe's exit status is `npm`'s, not `tee`'s.
|
|
555
705
|
if ! npm install -g "$CLI_PACKAGE" 2>&1 | tee -a "$BEEOS_INSTALL_LOG"; then
|
|
706
|
+
# ── EACCES auto-recovery ──────────────────────────────────────
|
|
707
|
+
# The single most common Phase 3 failure: the system npm prefix
|
|
708
|
+
# is owned by root, EACCES on rename. Detect the fingerprint in
|
|
709
|
+
# the npm log we just teed; if it matches, swap the prefix to a
|
|
710
|
+
# user-owned dir (see `recover_eacces_prefix` for the rationale)
|
|
711
|
+
# and retry exactly once. Every other npm error class
|
|
712
|
+
# (ENOSPC / ETIMEDOUT / EBADENGINE / ...) falls through to the
|
|
713
|
+
# hint+exit path below — those are rarer in practice and need
|
|
714
|
+
# human judgement we can't safely automate.
|
|
715
|
+
if grep -qE 'EACCES.*permission denied.*node_modules' "$BEEOS_INSTALL_LOG"; then
|
|
716
|
+
warn "Detected EACCES on global node_modules — attempting auto-recovery."
|
|
717
|
+
if recover_eacces_prefix; then
|
|
718
|
+
info "Retrying npm install with user-owned prefix..."
|
|
719
|
+
if npm install -g "$CLI_PACKAGE" 2>&1 | tee -a "$BEEOS_INSTALL_LOG"; then
|
|
720
|
+
send_telemetry "install.bootstrap.npm_recovered" "eacces_prefix"
|
|
721
|
+
send_telemetry "install.bootstrap.cli_installed"
|
|
722
|
+
exec beeos "$subcmd" "$@" <"$stdin_src"
|
|
723
|
+
fi
|
|
724
|
+
fi
|
|
725
|
+
fi
|
|
726
|
+
# ── End recovery ──────────────────────────────────────────────
|
|
727
|
+
|
|
556
728
|
# P0-A of the install-link review: separate "Node ready" from
|
|
557
729
|
# "CLI is actually on PATH" so the dashboard never reports a
|
|
558
730
|
# success that the user can't reproduce. `install.bootstrap.npm_fail`
|
|
@@ -565,7 +737,10 @@ run_cli() {
|
|
|
565
737
|
error " - EEXIST on /bin/beeos from another package:"
|
|
566
738
|
error " npm uninstall -g @beeos-ai/cli # then re-run installer"
|
|
567
739
|
error " - EACCES permission error:"
|
|
568
|
-
error "
|
|
740
|
+
error " auto-recovery already attempted; if it failed, manually"
|
|
741
|
+
error " run \`npm config set prefix \"\$HOME/.npm-global\"\` and"
|
|
742
|
+
error " add \$HOME/.npm-global/bin to PATH, then re-run installer."
|
|
743
|
+
error " (Set BEEOS_NO_NPM_PREFIX_FIX=1 to disable auto-recovery.)"
|
|
569
744
|
exit 3
|
|
570
745
|
fi
|
|
571
746
|
# NPM succeeded — CLI is now on PATH. The device-agent suite is
|