Shell 1.12.1: a catalog install keeps the tarball's file modes
parseTar threw away each entry's mode, so installModule wrote every downloaded file 0644 — including the prebuilt helpers modules ship under bin/. spawn() then failed with EACCES: on the 1.10.2 rollout (AB, 2026-10-04) that silently killed both LTC capture (Timers 1.7.0) and SPL metering (0.5.0) until the binaries were chmod +x'd by hand. carryOverExecutables (1.9.2) never applied: it only carries files the download does NOT include, and these downloads include the helpers. parseTar now keeps the header's mode field (permission bits only, defaulting to 0644) and both writers pass it on: installModule's staging and the archive-based shell update. The unbundled-shell test's mock repo now ships an executable bin/ helper and asserts it still spawns-worthy after a catalog install, alongside the existing carry-over check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c9e03398a6
commit
34a6def839
+22
-1
@@ -12,7 +12,12 @@
|
||||
* 2. a shell update from an archive that carries modules/ never writes
|
||||
* them into the app folder — they show up in the catalog instead;
|
||||
* 3. a fresh install lists everything the repo offers, and installing
|
||||
* several at once lands them in the data directory and mounts them.
|
||||
* several at once lands them in the data directory and mounts them;
|
||||
* 4. a module downloaded from the catalog keeps the tarball's modes — a
|
||||
* shipped bin/ helper is still executable (0644 means spawn EACCES,
|
||||
* which is what broke LTC and SPL on the 1.10.2 rollout) — while
|
||||
* per-machine executables the download does not include are still
|
||||
* carried over from the copy being replaced.
|
||||
*
|
||||
* node tools/unbundled.test.js
|
||||
*
|
||||
@@ -70,6 +75,11 @@ function buildArchive(dir) {
|
||||
fs.copyFileSync(path.join(REPO, 'server.js'), path.join(top, 'server.js'));
|
||||
fs.writeFileSync(path.join(top, 'package.json'), JSON.stringify({ name: 'proddash', version: '9.9.9' }));
|
||||
writeModule(path.join(top, 'modules', 'demo'), 'demo', '2.0.0');
|
||||
// The repo ships this one prebuilt (the way Timers and SPL ship their
|
||||
// capture helpers): it must come out of the download still executable.
|
||||
fs.mkdirSync(path.join(top, 'modules', 'demo', 'bin'));
|
||||
fs.writeFileSync(path.join(top, 'modules', 'demo', 'bin', 'demo-capture'), '#!/bin/sh\necho capture\n');
|
||||
fs.chmodSync(path.join(top, 'modules', 'demo', 'bin', 'demo-capture'), 0o755);
|
||||
writeModule(path.join(top, 'modules', 'other'), 'other', '1.0.0');
|
||||
writeModule(path.join(top, 'modules', 'third'), 'third', '1.0.0');
|
||||
const out = path.join(dir, 'main.tar.gz');
|
||||
@@ -188,6 +198,17 @@ function buildPackagedApp(app) {
|
||||
const empty = await api('/api/admin/modules/install', { method: 'POST', body: '{}' });
|
||||
assert.equal(empty.status, 400, 'nothing to install is said');
|
||||
|
||||
// ── 4. a download keeps executable modes, and carry-over still works ──
|
||||
const upd = await api('/api/admin/modules/demo/install', { method: 'POST', body: '{}' });
|
||||
assert.equal(upd.status, 200, 'demo update: ' + JSON.stringify(upd.body));
|
||||
assert.equal(upd.body.version, '2.0.0', 'demo was updated from the repo');
|
||||
const shipped = path.join(dataDir, 'modules', 'demo', 'bin', 'demo-capture');
|
||||
assert.ok(fs.existsSync(shipped), 'the downloaded helper is there');
|
||||
assert.ok(fs.statSync(shipped).mode & 0o111, 'and the tarball\'s execute bit survived the install');
|
||||
const carried = path.join(dataDir, 'modules', 'demo', 'bin', 'demo-helper');
|
||||
assert.ok(fs.existsSync(carried), 'the per-machine executable the download lacks was carried over');
|
||||
assert.ok(fs.statSync(carried).mode & 0o111, 'still executable');
|
||||
|
||||
console.log('unbundled shell: every check passed');
|
||||
} catch (err) {
|
||||
failed = true;
|
||||
|
||||
Reference in New Issue
Block a user