Off-box backup: pin the host key, guarantee the remote dir, page on failure
PHASE 1 — HOST KEY STATICALLY PINNED. ssh-keyscan -p 23 returned an ED25519 key whose fingerprint EQUALS the out-of-band value SHA256:XqONwb1S0zuj5A1CDxpOSuD2hnAArV1A3wKY7Z3sdgM, so it is safe to pin. scripts/storagebox_known_hosts now carries that verified line and ships to the container (Dockerfile already COPYs scripts/). backup-db.sh uses StrictHostKeyChecking=yes + UserKnownHostsFile=<pin> instead of accept-new, which was trust-on-first-use and would have accepted an impostor on the very first run. A missing pin file REFUSES the push rather than silently falling back. Never weakened to accept-new/=no//dev/null — a test asserts that on executable lines. PHASE 1b — REMOTE DIR GUARANTEED. The box has only .ssh/, and rsyncing a file into a missing parent either fails or silently writes the dump AS the directory name — one file, overwritten nightly, reading as "backups exist" while retaining exactly one. Uses rsync --mkpath when available, else an explicit remote mkdir -p ahead of the push. PHASE 2b — FAILED OFF-BOX PUSH IS NOW LOUD. Off-box is required, so the failed-push path pages at "urgent" (was "low"/deferred) and the script emits a machine-readable OFFBOX_OK=1/0/deferred that POST /api/internal/backup/run surfaces as a distinct offbox_ok field. Exit code deliberately still reflects ON-BOX durability — a good on-box dump must not raise a false total-failure alarm. Surfacing the truth, not manufacturing a failure. No key material is echoed anywhere; only the PUBLIC host key is committed. Suite 280/3338 green, build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
This commit is contained in:
@@ -255,8 +255,16 @@ router.post('/backup/run', async (req, res) => {
|
||||
try {
|
||||
const started = Date.now();
|
||||
const result = await runBackup();
|
||||
// Session 64 Phase 2b — off-box is REQUIRED now, so its outcome is reported
|
||||
// SEPARATELY from the exit code. The script deliberately still exits 0 on a
|
||||
// failed push (a durable on-box dump must not raise a false total-failure
|
||||
// alarm) — so without this field a failed off-box push reads as success.
|
||||
const tail = result.tail || '';
|
||||
const offboxOk = /OFFBOX_OK=1/.test(tail) ? true
|
||||
: (/OFFBOX_OK=0/.test(tail) ? false : null); // null = deferred/not attempted
|
||||
return res.json({
|
||||
ok: result.ok,
|
||||
offbox_ok: offboxOk,
|
||||
exit_code: result.code,
|
||||
duration_ms: Date.now() - started,
|
||||
durability_warning: durabilityWarning() || null,
|
||||
|
||||
Reference in New Issue
Block a user