diff --git a/bin/restore-linux.sh b/bin/restore-linux.sh index 9711ef4..30c19ca 100644 --- a/bin/restore-linux.sh +++ b/bin/restore-linux.sh @@ -82,20 +82,6 @@ EOF echo "Registered promethean in ~/.bashrc." fi - # Same ncp inside PowerShell, if pwsh is installed here. bash functions are - # invisible to pwsh, so without this `ncp` is "not recognized" there even - # though it works in bash. install-windows.ps1 does this on the Windows side; - # this profile lives outside the repo, so a restore has to re-add it. - if command -v pwsh >/dev/null; then - ps_profile="$HOME/.config/powershell/profile.ps1" - if ! grep -q "NexusOS ncp" "$ps_profile" 2>/dev/null; then - mkdir -p "$(dirname "$ps_profile")" - printf '\n# NexusOS ncp\nfunction ncp { & "%s/management/ncp.ps1" @args }\n' \ - "$NEXUS_ROOT" >> "$ps_profile" - echo "Registered ncp in $ps_profile." - fi - fi - # The AMD box runs CPU-only (Vega 20, 4GB VRAM thrashes). An NVIDIA box should not. if command -v nvidia-smi >/dev/null && command -v sqlite3 >/dev/null && \ [ "$(sqlite3 "$NEXUS_ROOT/synapse/memory/memory.db" \ diff --git a/install-windows.ps1 b/install-windows.ps1 index 269d1c8..99701da 100644 --- a/install-windows.ps1 +++ b/install-windows.ps1 @@ -127,7 +127,8 @@ Write-OK "Web UI built (interface\web\dist)" # management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the # PowerShell profile. Three reasons the profile route kept biting: # 1. Execution policy defaults to Restricted, which blocks the profile itself - -# so the function was never defined, and ncp.ps1 could not have run anyway. +# so the function was never defined, and the .ps1 it called could not +# have run anyway. # 2. Profiles are a PowerShell thing. cmd, Win+R, Task Scheduler and .lnk # targets all reported "ncp is not recognized". # 3. We are elevated here, so $PROFILE is the ADMIN's profile - the whole diff --git a/management/ncp.cmd b/management/ncp.cmd index ab86734..08c3de2 100644 --- a/management/ncp.cmd +++ b/management/ncp.cmd @@ -3,7 +3,12 @@ REM ncp - PATH entry point on Windows. A .cmd, not a .ps1, on purpose: PowerShel REM execution policy governs .ps1 only, so this keeps working under the default REM Restricted policy and from cmd, Win+R, Task Scheduler and .lnk targets - none REM of which load a PowerShell profile. install-windows.ps1 puts this directory on -REM the machine PATH. ncp.ps1 stays for pwsh-on-Linux, where there is no PATH shim. +REM the machine PATH. +REM +REM This file must NOT be accompanied by an ncp.ps1 in the same directory: +REM PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), so a +REM sibling ncp.ps1 wins in PowerShell and drags the execution policy back in - +REM exactly what this file exists to avoid. REM REM ASCII only, same rule as the .ps1 files - a test in tests/test_smoke.py enforces it. setlocal diff --git a/management/ncp.ps1 b/management/ncp.ps1 deleted file mode 100644 index a65b784..0000000 --- a/management/ncp.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -# ncp - PowerShell entry point. The CLI itself is management/ncp.py, the same -# file the Linux shell wrapper runs; this only picks an interpreter and forwards -# the arguments. -# -# On Windows, ncp comes from management/ncp.cmd on the machine PATH instead (a -# .cmd is not subject to the execution policy and works outside PowerShell too) - -# install-windows.ps1 sets that up. This file is what gives ncp to pwsh on Linux, -# where restore-linux.sh registers it in $PROFILE.CurrentUserAllHosts as: -# -# function ncp { & "/management/ncp.ps1" @args } -# -# ASCII only, no exceptions: PowerShell 5.1 decodes BOM-less files as ANSI, so a -# single Unicode dash eats a quote and the script dies at parse time. A test in -# tests/test_smoke.py fails if any .ps1 in this repo gains a non-ASCII byte. - -$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) - -# Forward slashes and per-platform venv layout: backslash is a literal character -# on Linux, not a separator, so "Promethean\Scripts\python.exe" would resolve to -# a single nonsense filename under pwsh on Linux. Windows accepts "/" happily. -# $env:OS is the 5.1-safe check; the automatic $IsWindows only exists on PS 6+. -$OnWindows = ($env:OS -eq "Windows_NT") -if ($OnWindows) { - $Py = Join-Path $Root "Promethean/Scripts/python.exe" -} else { - $Py = Join-Path $Root "Promethean/bin/python3" -} - -# Fall back to a system Python so backup/restore still work before the venv is -# built - those delegate to the stdlib-only bin/sync.py. -if (-not (Test-Path $Py)) { - foreach ($candidate in @("python3", "python")) { - $found = Get-Command $candidate -ErrorAction SilentlyContinue - if ($found) { $Py = $found.Source; break } - } -} -if (-not (Test-Path $Py)) { - Write-Error "No Python found. Run install.sh (Linux) or install-windows.ps1 first." - exit 1 -} - -& $Py (Join-Path $Root "management/ncp.py") @args -exit $LASTEXITCODE diff --git a/management/nexus-cli.sh b/management/nexus-cli.sh index 55395b5..c8f3b04 100644 --- a/management/nexus-cli.sh +++ b/management/nexus-cli.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ncp - Linux entry point. The CLI itself is management/ncp.py, which runs -# unchanged on Windows too (see management/ncp.ps1); this stays a shell script +# unchanged on Windows too (see management/ncp.cmd); this stays a shell script # because ~/.bashrc, launch_nexus.sh, bin/restore-linux.sh, controlpanel.py, # bin/panel/nexus-popup.py and management/nexus-app.sh all invoke this path. # diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 12c817c..cda97b9 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -96,6 +96,18 @@ def test_ncp_is_registered_on_path_not_in_a_shell_profile(): "model pull must come after the desktop shortcut" +def test_no_ps1_shadows_the_ncp_path_shim(): + """PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), so + an ncp.ps1 sitting next to ncp.cmd wins in PowerShell and drags the execution + policy back in - the exact thing the .cmd exists to avoid. Observed on the + Windows VM: `Get-Command ncp -All` listed ncp.ps1 first, from the same + directory the installer had just put on PATH.""" + shim = REPO_ROOT / "management" / "ncp.cmd" + assert shim.exists(), "the Windows PATH shim is missing" + twin = shim.with_suffix(".ps1") + assert not twin.exists(), f"{twin.name} shadows {shim.name} in PowerShell" + + def test_first_playbook_is_the_system_prompt(tmp_path, monkeypatch): store = PlaybookFileStore(tmp_path) @@ -268,7 +280,7 @@ def test_desktop_stage_is_the_only_one_touching_home(): # and in the PowerShell profile is how you launch NexusOS at all, and each is # a grep-guarded no-op on re-run. Desktop config (xfconf, plank, themes, # os-release) must stay in the desktop stage so --no-desktop really is safe. - shell_wiring = (".bashrc", "powershell/profile.ps1") + shell_wiring = (".bashrc",) home_writes = [ln for ln in runtime.splitlines() if "$HOME" in ln and not any(w in ln for w in shell_wiring)] assert not home_writes, f"runtime stage writes to $HOME: {home_writes}"