Make the off-site backup find Drive, and say so when it cannot
The nightly copy to Drive had been dead since 15 September. DRIVE_DIR pointed at Apps/Backups/Checklist Backups; the folder is Apps/Checklist Backups, with no Backups level. The guard for a missing folder did its job and exited — into a logfile nobody reads, so from the outside a broken backup and a healthy no-op looked identical. Eight days were missing; this run copied them. That is the second time the same failure has hidden in the same place. The header comment already records the first: ten weeks stale in July, found in September. Correcting the path alone would leave the mechanism that hid it untouched, so: - The Drive folder is probed rather than hard-coded, across every mounted GoogleDrive-* account and both known folder layouts. The laptop mounts christian@ where the Studio mounts media@, and the folder has moved once already, so a literal path is the wrong thing to depend on. - Every failure now emails as well as logs, via scripts/alert.js, which reuses the server's own SMTP settings. A failed alert never masks the failure it was reporting. - A run that does not error but leaves the newest off-site copy more than STALE_DAYS old now alerts too. That is the check that would have caught both incidents; the error paths only fire when the script actually runs. What this still cannot catch is the script never running at all — a sleeping Mac or an unloaded LaunchAgent is silent by construction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1d40872127
commit
3d82e46951
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
// alert.js — send one alert email, reusing the server's own SMTP settings.
|
||||
//
|
||||
// Exists because backup-to-drive.sh used to report failure only into
|
||||
// ~/Library/Logs/checklist-drive-backup.log, where nobody was ever going to look. That is
|
||||
// how the Drive copies went ten weeks stale in July 2026, and then — despite the script
|
||||
// written to prevent exactly that — another nine days in September 2026.
|
||||
//
|
||||
// Usage: node scripts/alert.js "<subject>" "<body>"
|
||||
// Exit 0 if the mail went out, 1 otherwise. Callers should NOT fail on a failed alert:
|
||||
// the thing being reported matters more than the reporting of it.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const ENV_FILE = path.join(ROOT, 'server', '.env');
|
||||
|
||||
// Minimal .env reader — the server uses dotenv, but this script has to run from a plain
|
||||
// LaunchAgent with no cwd guarantees and no node_modules of its own.
|
||||
function readEnv(file) {
|
||||
const out = {};
|
||||
let text;
|
||||
try { text = fs.readFileSync(file, 'utf8'); }
|
||||
catch { return out; }
|
||||
for (const raw of text.split('\n')) {
|
||||
const line = raw.trim();
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
const eq = line.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
let v = line.slice(eq + 1).trim();
|
||||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
||||
out[line.slice(0, eq).trim()] = v;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const subject = process.argv[2];
|
||||
const body = process.argv[3] || '';
|
||||
if (!subject) { console.error('alert.js: no subject given'); process.exit(1); }
|
||||
|
||||
const env = readEnv(ENV_FILE);
|
||||
const to = env.REPORT_EMAIL;
|
||||
if (!to) { console.error(`alert.js: no REPORT_EMAIL in ${ENV_FILE}`); process.exit(1); }
|
||||
if (!env.SMTP_HOST || !env.SMTP_USER || !env.SMTP_PASS) {
|
||||
console.error(`alert.js: SMTP is not configured in ${ENV_FILE}`); process.exit(1);
|
||||
}
|
||||
|
||||
// nodemailer lives in server/node_modules; this script has none of its own.
|
||||
let nodemailer;
|
||||
try { nodemailer = require(path.join(ROOT, 'server', 'node_modules', 'nodemailer')); }
|
||||
catch { console.error('alert.js: nodemailer not installed — run `npm install` in server/'); process.exit(1); }
|
||||
|
||||
const transport = nodemailer.createTransport({
|
||||
host: env.SMTP_HOST,
|
||||
port: Number(env.SMTP_PORT || 587),
|
||||
secure: String(env.SMTP_SECURE || 'false') === 'true',
|
||||
auth: { user: env.SMTP_USER, pass: env.SMTP_PASS },
|
||||
});
|
||||
|
||||
await transport.sendMail({
|
||||
from: env.SMTP_FROM || env.SMTP_USER,
|
||||
to,
|
||||
subject,
|
||||
text: `${body}\n\n-- \nSent by scripts/alert.js on ${require('os').hostname()} at ${new Date().toISOString()}\n`,
|
||||
});
|
||||
console.log(`alert.js: sent "${subject}" to ${to}`);
|
||||
}
|
||||
|
||||
main().catch(err => { console.error('alert.js:', err && err.message ? err.message : err); process.exit(1); });
|
||||
Reference in New Issue
Block a user