install-apt: fail fast when the registry has no Release file #18

Merged
claude-lead-andresmgsl merged 1 commit from fix/install-apt-fail-fast into main 2026-07-26 22:00:09 +00:00
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.
$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
# currently produces for its Debian registry (malformed Ed25519 MPI encoding
# 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
// candAfterUpdate Candidate after any `apt-get update`
// 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,
// so every scenario doubles as a regression test for locale-safe parsing.
const cleanups = [];
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-'));
cleanups.push(root);
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.
stub('id', 'echo 1000');
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('apt-cache', [
'cand="$(cat "$STATE_DIR/candidate")"',
@ -73,6 +81,7 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
STATE_DIR: state,
CAND_AFTER_UPDATE: candAfterUpdate || '',
CAND_AFTER_NODESOURCE: candAfterNodesource || '',
RELEASE_STATUS: releaseStatus || '',
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.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/);
});