Security headers, cross-site write refusal, atomic JSON writes, admin backup download, /healthz and Docker health check, OnlyOffice requires a JWT secret, npm test, CI audit, arm64 hub image. New Links tab for personal bookmarks, saved with per-person settings. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
18 lines
1.2 KiB
JavaScript
18 lines
1.2 KiB
JavaScript
// Admin-only backup: streams the whole data folder (passkeys, accounts, tasks, events, settings, Signal history) as a .tar.gz.
|
|
// Treat the file like a password: it contains the session secret and encrypted sign-in tokens.
|
|
const { spawn } = require('child_process');
|
|
const path = require('path');
|
|
const DATA_DIR = path.resolve(process.env.DATA_DIR || path.join(__dirname, 'data'));
|
|
function handle(req, res) {
|
|
if (req.method !== 'GET') { res.writeHead(405); return res.end(); }
|
|
const name = 'nehub-backup-' + new Date().toISOString().slice(0, 10) + '.tar.gz';
|
|
const tar = spawn('tar', ['-czf', '-', '--exclude=./lyrics', '--exclude=*.tmp', '-C', DATA_DIR, '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
tar.on('error', e => { if (!res.headersSent) { res.writeHead(500, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'Could not make the backup: ' + e.message })); } else res.destroy(); });
|
|
tar.stderr.on('data', d => console.warn('[backup]', String(d).trim()));
|
|
res.writeHead(200, { 'content-type': 'application/gzip', 'content-disposition': `attachment; filename="${name}"`, 'cache-control': 'no-store' });
|
|
tar.stdout.pipe(res);
|
|
res.on('close', () => tar.kill());
|
|
console.log('[backup] downloaded');
|
|
}
|
|
module.exports = { handle };
|