install-apt: fail fast with a clear message when the registry has no Release file

This commit is contained in:
kimi-reviewer-andresmgsl 2026-07-26 21:41:16 +00:00
parent f4b0bdbe4e
commit 8255c568b1
2 changed files with 39 additions and 2 deletions

View file

@ -88,6 +88,19 @@ echo "deb [signed-by=$KEYRING] $FORGE_URL/api/packages/$OWNER/debian $DISTRIBUTI
# read these. # read these.
$SUDO chmod 0644 "$KEYRING" "$LIST" $SUDO chmod 0644 "$KEYRING" "$LIST"
# Fail fast with a clear message when the registry has no package published
# yet: without a Release file, `apt-get update` would only fail with a
# generic "repository does not have a Release file" error. A definitive 404
# is fatal; any other curl outcome (e.g. a network hiccup) is left for
# apt-get update to report.
RELEASE_URL="$FORGE_URL/api/packages/$OWNER/debian/dists/$DISTRIBUTION/Release"
if [ "$(curl -sSL -o /dev/null -w '%{http_code}' "$RELEASE_URL" || true)" = "404" ]; then
echo "error: no stoke package has been published to the $OWNER Debian registry yet" >&2
echo "($RELEASE_URL returned 404)." >&2
echo "Install stoke via npm or manually instead — see the README." >&2
exit 1
fi
# Newer apt verifies with sqv (Sequoia), which rejects the signature Forgejo # Newer apt verifies with sqv (Sequoia), which rejects the signature Forgejo
# currently produces for its Debian registry (malformed Ed25519 MPI encoding # currently produces for its Debian registry (malformed Ed25519 MPI encoding
# in the upstream signing library). Try the properly signed source first so # in the upstream signing library). Try the properly signed source first so

View file

@ -12,12 +12,13 @@ const SCRIPT = path.join(__dirname, '..', 'scripts', 'install-apt.sh');
// candInitial `apt-cache policy` Candidate before any update // candInitial `apt-cache policy` Candidate before any update
// candAfterUpdate Candidate after any `apt-get update` // candAfterUpdate Candidate after any `apt-get update`
// candAfterNodesource Candidate after an update once nodesource.list exists // candAfterNodesource Candidate after an update once nodesource.list exists
// releaseStatus HTTP status curl reports for the registry Release file
// The apt-cache stub localizes the "Candidate:" label unless LC_ALL=C is set, // The apt-cache stub localizes the "Candidate:" label unless LC_ALL=C is set,
// so every scenario doubles as a regression test for locale-safe parsing. // so every scenario doubles as a regression test for locale-safe parsing.
const cleanups = []; const cleanups = [];
process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); }); process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); });
function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexistingNodesourceList }) { function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexistingNodesourceList, releaseStatus }) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-apt-test-')); const root = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-apt-test-'));
cleanups.push(root); cleanups.push(root);
const bin = path.join(root, 'bin'); const bin = path.join(root, 'bin');
@ -39,7 +40,14 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
// Force the non-root path so every mutation goes through the sudo stub. // Force the non-root path so every mutation goes through the sudo stub.
stub('id', 'echo 1000'); stub('id', 'echo 1000');
stub('sudo', 'exec "$@"'); stub('sudo', 'exec "$@"');
stub('curl', 'echo "FAKE-KEY"'); // Registry Release-file probes (URLs under /dists/) answer with the
// scenario's HTTP status; everything else is a key fetch.
stub('curl', [
'for a in "$@"; do',
' case "$a" in */dists/*) echo "${RELEASE_STATUS:-200}"; exit 0;; esac',
'done',
'echo "FAKE-KEY"',
].join('\n'));
stub('stoke', 'echo 1.2.0'); stub('stoke', 'echo 1.2.0');
stub('apt-cache', [ stub('apt-cache', [
'cand="$(cat "$STATE_DIR/candidate")"', 'cand="$(cat "$STATE_DIR/candidate")"',
@ -73,6 +81,7 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
STATE_DIR: state, STATE_DIR: state,
CAND_AFTER_UPDATE: candAfterUpdate || '', CAND_AFTER_UPDATE: candAfterUpdate || '',
CAND_AFTER_NODESOURCE: candAfterNodesource || '', CAND_AFTER_NODESOURCE: candAfterNodesource || '',
RELEASE_STATUS: releaseStatus || '',
LC_ALL: 'es_ES.UTF-8', // localized environment; the script must force C LC_ALL: 'es_ES.UTF-8', // localized environment; the script must force C
}, },
}); });
@ -147,3 +156,18 @@ test('pre-existing user-managed nodesource.list is never overwritten', () => {
assert.equal(s.nodesourceList, marker); assert.equal(s.nodesourceList, marker);
assert.doesNotMatch(s.aptGetLog, /install -y stoke/); assert.doesNotMatch(s.aptGetLog, /install -y stoke/);
}); });
test('registry Release file 404s: fails fast with a clear message before apt runs', () => {
const s = runScenario({ candInitial: '22.23.1-1nodesource1', releaseStatus: '404' });
assert.notEqual(s.res.status, 0);
assert.match(s.res.stderr, /no stoke package has been published/);
assert.match(s.res.stderr, /npm/);
assert.match(s.res.stderr, /dists\/stable\/Release returned 404/);
assert.equal(s.aptGetLog, '', 'must abort before any apt-get invocation');
});
test('registry Release file present: proceeds with the install', () => {
const s = runScenario({ candInitial: '22.23.1-1nodesource1', releaseStatus: '200' });
assert.equal(s.res.status, 0, s.res.stderr);
assert.match(s.aptGetLog, /install -y stoke/);
});