35 lines
2.2 KiB
TypeScript
35 lines
2.2 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import { one } from '../../core/db.js';
|
|
import { requirePermission } from '../../core/auth.js';
|
|
import { query } from '../../core/db.js';
|
|
import { readFile } from 'node:fs/promises';
|
|
import type { KcModule } from '../../core/module.js';
|
|
|
|
/** Backup-Zustand aus der Statusdatei des Backup-Laufs (siehe docs/betrieb-backup-restore.md). Ohne Datei: nicht eingerichtet. */
|
|
async function backupState() {
|
|
const file = process.env.BACKUP_STATUS_FILE ?? '/var/lib/kundencenter/backup-status.json';
|
|
let st: any; try { st = JSON.parse(await readFile(file, 'utf8')); } catch { return { configured: false as const }; }
|
|
const hours = (iso?: string) => (iso ? (Date.now() - new Date(iso).getTime()) / 3600000 : null);
|
|
const run = st.lastRun; const test = st.lastRestoreTest;
|
|
return {
|
|
configured: true as const, lastRunAt: run?.at ?? null, ok: !!run?.ok, ageHours: hours(run?.at), stale: !run || !run.ok || (hours(run.at) ?? 999) > 26, error: run?.error ?? null,
|
|
file: run?.file ?? null, sizeBytes: run?.sizeBytes ?? null, targets: (run?.targets ?? []).map((t: any) => ({ name: t.name, ok: t.ok })),
|
|
lastRestoreTestAt: test?.at ?? null, restoreOk: test ? !!test.ok : null, restoreStale: !test || !test.ok || (hours(test.at) ?? 999) > 24 * 10, restoreError: test?.error ?? null,
|
|
};
|
|
}
|
|
|
|
export const systemModule: KcModule = {
|
|
name: 'system',
|
|
register(app: FastifyInstance) {
|
|
app.get('/health', async () => ({ status: 'ok' })); // Liveness
|
|
app.get('/ready', async (_req, reply) => { // Readiness: DB erreichbar
|
|
try { await one('SELECT 1 AS ok'); return { status: 'ready' }; } catch { return reply.code(503).send({ status: 'db_unavailable' }); }
|
|
});
|
|
app.get('/admin/system', async (req) => {
|
|
requirePermission(req, 'jobs.read');
|
|
const jobs = await query('SELECT status, COUNT(*) AS n FROM jobs GROUP BY status');
|
|
const oldest = await one('SELECT MIN(run_at) AS t FROM jobs WHERE status IN (\'scheduled\',\'retrying\')');
|
|
return { jobs: Object.fromEntries(jobs.map((j) => [j.status, Number(j.n)])), oldestPendingJob: oldest?.t ?? null, serverTime: new Date().toISOString(), backup: await backupState() };
|
|
});
|
|
},
|
|
};
|