fix: keep apt signature verification on transient failures
All checks were successful
labels / labels (pull_request) Successful in 9s
ci / test (pull_request) Successful in 14s

This commit is contained in:
codex-bot-andresmgsl 2026-08-30 11:31:24 +00:00
parent c09943ea32
commit acb46d0707
2 changed files with 48 additions and 4 deletions

View file

@ -104,9 +104,19 @@ 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
# this heals automatically once the forge is fixed; otherwise fall back to
# [trusted=yes] — package integrity then relies on HTTPS to our own forge.
if ! update_only_source "$LIST"; then
# this heals automatically once the forge is fixed. Only that signature-error
# class permits the compatibility fallback; auth, network, and other failures
# must leave verification enabled and retain apt's original diagnostic.
if update_output="$(update_only_source "$LIST" 2>&1)"; then
printf '%s\n' "$update_output"
else
update_status=$?
if ! grep -Eiq \
'NO_PUBKEY|EXPKEYSIG|BADSIG|signatures? (could not|couldn.t) be verified|signature (verification )?(failed|failure|error|invalid)|repository .*not signed|is not signed' \
<<<"$update_output"; then
printf '%s\n' "$update_output" >&2
exit "$update_status"
fi
echo
echo "WARNING: signature verification failed (known Forgejo registry issue" >&2
echo "with sqv-based apt). Falling back to [trusted=yes]; transport" >&2

View file

@ -13,12 +13,13 @@ const SCRIPT = path.join(__dirname, '..', 'scripts', 'install-apt.sh');
// 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
// sourceUpdateError stderr and exit 100 for the first signed stoke update
// 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, releaseStatus }) {
function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexistingNodesourceList, releaseStatus, sourceUpdateError }) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-apt-test-'));
cleanups.push(root);
const bin = path.join(root, 'bin');
@ -58,8 +59,16 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
].join('\n'));
stub('apt-get', [
'echo "apt-get $*" >> "$STATE_DIR/apt-get.log"',
'source_list=""',
'for a in "$@"; do',
' case "$a" in Dir::Etc::sourcelist=*) source_list="${a#*=}";; esac',
'done',
'for a in "$@"; do',
' if [ "$a" = update ]; then',
' if [ -n "$source_list" ] && grep -q "signed-by=" "$source_list" && [ -n "${SOURCE_UPDATE_ERROR:-}" ]; then',
' printf "%s\\n" "$SOURCE_UPDATE_ERROR" >&2',
' exit 100',
' fi',
' if [ -e "$STOKE_APT_ETC/sources.list.d/nodesource.list" ] && [ -n "${CAND_AFTER_NODESOURCE:-}" ]; then',
' echo "$CAND_AFTER_NODESOURCE" > "$STATE_DIR/candidate"',
' elif [ -n "${CAND_AFTER_UPDATE:-}" ]; then',
@ -82,6 +91,7 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
CAND_AFTER_UPDATE: candAfterUpdate || '',
CAND_AFTER_NODESOURCE: candAfterNodesource || '',
RELEASE_STATUS: releaseStatus || '',
SOURCE_UPDATE_ERROR: sourceUpdateError || '',
LC_ALL: 'es_ES.UTF-8', // localized environment; the script must force C
},
});
@ -96,6 +106,7 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
nodesourceKey: read(path.join(aptEtc, 'keyrings', 'nodesource.asc')),
nodesourceKeyMode: mode(path.join(aptEtc, 'keyrings', 'nodesource.asc')),
forgeKeyMode: mode(path.join(aptEtc, 'keyrings', 'forgejo-heavy-duty.asc')),
forgeList: read(path.join(aptEtc, 'sources.list.d', 'forgejo-heavy-duty.list')),
aptGetLog: read(path.join(state, 'apt-get.log')) || '',
};
// Drop the throwaway tree after we have read everything we need.
@ -171,3 +182,26 @@ test('registry Release file present: proceeds with the install', () => {
assert.equal(s.res.status, 0, s.res.stderr);
assert.match(s.aptGetLog, /install -y stoke/);
});
test('signature verification failure alone may use the trusted compatibility fallback', () => {
const s = runScenario({
candInitial: '22.23.1-1nodesource1',
sourceUpdateError: 'W: GPG error: signatures could not be verified: NO_PUBKEY DEADBEEF\nE: The repository is not signed.',
});
assert.equal(s.res.status, 0, s.res.stderr);
assert.match(s.forgeList, /\[trusted=yes\]/);
assert.match(s.aptGetLog, /install -y stoke/);
});
test('network update failure stays fatal and never disables signature verification', () => {
const failure = 'Temporary failure resolving forgejo.heavyduty.builders';
const s = runScenario({
candInitial: '22.23.1-1nodesource1',
sourceUpdateError: failure,
});
assert.notEqual(s.res.status, 0);
assert.match(s.res.stderr, new RegExp(failure));
assert.match(s.forgeList, /\[signed-by=/);
assert.doesNotMatch(s.forgeList, /trusted=yes/);
assert.doesNotMatch(s.aptGetLog, /install -y stoke/);
});