diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c58a450..f7cdd57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ Releases before `0.2.5` predate the public launch; their notes live in the ## [Unreleased] +### Changed + +- `sysknife-setup` requires Node 22 or newer. Node 18 and 20 no longer receive + security fixes, so this drops support for them rather than retiring an + untested claim: `engines.node`, the preflight guard and all six published + support statements move together, and the refusal message says why the floor + moved. Nothing in CI is affected; every job that touches JavaScript already + runs Node 24 (#327). + ### Fixed - `sysknife audit verify` now reports `cannot_verify` and exits 2 over an empty diff --git a/README.md b/README.md index d3608390..aba21782 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,9 @@ plan and execute from chat. npx sysknife-setup ``` -Needs **Node 18 or newer**. On Ubuntu 22.04 `apt install nodejs` gives Node 12, -which is too old; the installer says so and how to get a current Node. No Rust +Needs **Node 22 or newer**; older Node releases no longer receive security fixes. +On Ubuntu 22.04 `apt install nodejs` gives Node 12, which is too old; the installer +says so and how to get a current Node. No Rust toolchain and no compile: it downloads verified prebuilt binaries. [![npm version](https://img.shields.io/npm/v/sysknife-setup?style=flat-square&logo=npm)](https://www.npmjs.com/package/sysknife-setup) @@ -427,7 +428,7 @@ helps other people find the project. | Channel | Install | Notes | |---------|---------|-------| -| **npm** | `npx sysknife-setup` | [npmjs.com/package/sysknife-setup](https://www.npmjs.com/package/sysknife-setup) — setup wizard; needs Node 18+, no compile | +| **npm** | `npx sysknife-setup` | [npmjs.com/package/sysknife-setup](https://www.npmjs.com/package/sysknife-setup) — setup wizard; needs Node 22+, no compile | | **crates.io** | `cargo install sysknife-cli` / `cargo install sysknife-daemon` | Needs `build-essential`; ~7-12 min build. Published by reviewed version tags; see [docs/release.md](docs/release.md) | | **MCP Registry** | `io.github.lacs-project/sysknife` | [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) — resolves to the crates.io install above. Directory pages that sandbox a server list every tool but cannot call the ones needing the daemon; [docs/mcp-registry.md](docs/mcp-registry.md#what-a-directory-sandbox-can-and-cannot-tell-you) explains the split | | **GitHub Releases** | Download from [Releases](https://github.com/lacs-project/sysknife/releases) | Prebuilt x86_64 + aarch64 binaries with SHA-256 checksums on every tag | diff --git a/apps/sysknife-cli/README.md b/apps/sysknife-cli/README.md index 2bb0bcf1..ccb6c920 100644 --- a/apps/sysknife-cli/README.md +++ b/apps/sysknife-cli/README.md @@ -37,7 +37,7 @@ containers, 6m56s on 24.04 and 11m43s on 22.04. `cmake` is **not** required. Faster, and it installs both halves plus your MCP client config: ```sh -npx sysknife-setup # needs Node 18+ +npx sysknife-setup # needs Node 22+ ``` The wizard downloads SHA-256-verified binaries from the release page, so there diff --git a/docs/introduction.md b/docs/introduction.md index f280d465..deacc9e3 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -124,7 +124,7 @@ sudo systemctl enable --now sysknife-daemon sysknife "show disk usage" ``` -For prebuilt binaries instead of a build, run `npx sysknife-setup` (Node 18+). +For prebuilt binaries instead of a build, run `npx sysknife-setup` (Node 22+). See the canonical [Quick Start](quickstart.md). No API key needed if you have [Ollama](https://ollama.com) running locally — diff --git a/docs/mcp.md b/docs/mcp.md index 88744008..670ee125 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -182,7 +182,7 @@ the security boundary. npx sysknife-setup ``` -Needs Node 18 or newer (Ubuntu 22.04's apt Node is 12, which is too old). The +Needs Node 22 or newer (Ubuntu 22.04's apt Node is 12, which is too old). The wizard detects your installed `sysknife` binary, asks for the daemon socket and LLM provider, and then asks which integration to configure. No manual file editing needed. For install options and prerequisites in full, see the diff --git a/docs/quickstart.md b/docs/quickstart.md index 60621b9c..8d9668b8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -20,7 +20,7 @@ needs both. npx sysknife-setup ``` -**Prerequisites:** Node 18 or newer. On Ubuntu 22.04, `apt install nodejs` +**Prerequisites:** Node 22 or newer. On Ubuntu 22.04, `apt install nodejs` installs Node 12, which is too old — the installer will tell you so and how to get a current Node. It downloads SHA-256-verified binaries from the release page, installs the daemon service, and writes your MCP client config. diff --git a/packages/setup/node-preflight.js b/packages/setup/node-preflight.js index 111fa415..6ed56efa 100644 --- a/packages/setup/node-preflight.js +++ b/packages/setup/node-preflight.js @@ -15,7 +15,7 @@ */ /** Oldest Node the wizard's own syntax and APIs require. Keep in sync with `engines.node`. */ -var MIN_MAJOR = 18; +var MIN_MAJOR = 22; /** * Explain, actionably, why this Node cannot run the wizard. @@ -33,7 +33,12 @@ function unsupportedMessage(version) { if (!isNaN(major) && major >= MIN_MAJOR) return null; return ( - 'sysknife-setup needs Node ' + MIN_MAJOR + ' or newer. You are running ' + shown + '.\n' + + 'sysknife-setup needs Node ' + + MIN_MAJOR + + ' or newer because older releases no longer receive security fixes. ' + + 'You are running ' + + shown + + '.\n' + '\n' + "Ubuntu 22.04's `apt install nodejs` gives Node 12, which cannot run this\n" + 'installer. Pick whichever of these suits the machine:\n' + diff --git a/packages/setup/package.json b/packages/setup/package.json index 4a154422..7a9331ce 100644 --- a/packages/setup/package.json +++ b/packages/setup/package.json @@ -15,7 +15,7 @@ "sysknife-mcp": "mcp-launcher.js" }, "engines": { - "node": ">=18" + "node": ">=22" }, "files": [ "index.js", diff --git a/packages/setup/tests/node-preflight.test.mjs b/packages/setup/tests/node-preflight.test.mjs index 87ab4753..ee5f900a 100644 --- a/packages/setup/tests/node-preflight.test.mjs +++ b/packages/setup/tests/node-preflight.test.mjs @@ -44,17 +44,28 @@ test('rejects the Node that Ubuntu 22.04 ships, and says how to fix it', () => { const msg = unsupportedMessage('12.22.9'); assert.ok(msg, 'Node 12 must be rejected'); assert.match(msg, /12\.22\.9/, 'names the version actually in use'); - assert.match(msg, /\b18\b/, 'names the minimum'); + assert.match(msg, /\b22\b/, 'names the minimum'); + assert.match(msg, /end.of.life|security fixes/i, 'explains why the floor moved'); // Actionable: at least one runnable command, not just a complaint. assert.match(msg, /nodesource|nvm|snap|fnm/i, 'suggests a way to get a newer Node'); assert.match(msg, /releases/, 'offers the no-Node escape hatch (prebuilt binaries)'); }); test('rejects every major below the minimum and accepts every one at or above', () => { - for (const v of ['0.10.48', '4.9.1', '8.17.0', '12.22.9', '14.21.3', '16.20.2']) { + for (const v of [ + '0.10.48', + '4.9.1', + '8.17.0', + '12.22.9', + '14.21.3', + '16.20.2', + '18.20.8', + '20.19.5', + '21.7.3', + ]) { assert.ok(unsupportedMessage(v), `${v} must be rejected`); } - for (const v of ['18.0.0', '18.19.1', '20.11.0', '22.5.1', '24.0.0']) { + for (const v of ['22.0.0', '22.5.1', '24.0.0']) { assert.equal(unsupportedMessage(v), null, `${v} must be accepted`); } }); @@ -66,8 +77,25 @@ test('an unparseable version is not silently treated as supported', () => { }); test('the minimum matches what package.json advertises', () => { - assert.equal(MIN_MAJOR, 18); - assert.match(pkg.engines.node, />=\s*18/); + assert.equal(MIN_MAJOR, 22); + assert.match(pkg.engines.node, />=\s*22/); +}); + +test('published setup guidance names Node 22 and no longer claims Node 18 support', () => { + const repoRoot = path.resolve(pkgDir, '..', '..'); + const publishedClaims = [ + 'README.md', + 'docs/quickstart.md', + 'docs/mcp.md', + 'apps/sysknife-cli/README.md', + 'docs/introduction.md', + ]; + + for (const file of publishedClaims) { + const src = fs.readFileSync(path.join(repoRoot, file), 'utf8'); + assert.doesNotMatch(src, /Node 18/i, `${file} must not retain the old support floor`); + assert.match(src, /Node 22/i, `${file} must name the new support floor`); + } }); // ---------------------------------------------------------------------------