101%: app-level DDoS guard, PWA, /api/health, API docs, worker JAR backup
Beyond parity — the web-feasible versions of the "host-only" items plus extras AtomCMS doesn't have: - App-level abuse/DDoS guard (src/lib/services/abuse-guard.ts): counts requests per IP and auto-adds flooders to website_ip_blacklist (enforced by the access guard) + fires ddosDetected(). OFF by default, tunable via settings. The iptables layer stays host-only; this is the real app-tier mitigation. Access guard now also enforces the IP blacklist (cached). - PWA: a themeable web manifest (src/app/manifest.ts) + a service worker (public/sw.js, cache-first assets / network-first pages) registered after hydration — the hotel is now installable. - /api/health: DB + emulator(RCON) + runtime status probe. - /developers: a public API documentation page covering every REST endpoint with its method, path and auth requirement. - jobs-worker: daily emulator JAR backup (runs host-side in the worker, like AtomCMS's backup command) — copies + prunes; no-ops unless EMULATOR_JAR_PATH + EMULATOR_BACKUP_DIR are set. Verified live (prod, amx_test): /api/health ok, manifest + sw served, docs page renders, normal pages unaffected by the guard. tsc 0, vitest 49/49, next build 0.
This commit is contained in:
1 parent
4dfe698009
commit
54ec99de6d
11 files changed
+702
-1
No files matched your search
@@ -20,6 +20,8 @@
|
||||
* worker is the first consumer.
|
||||
*/
|
||||
import "dotenv/config";
|
||||
import { copyFile, mkdir, readdir, stat, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { Cron } from "croner";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { emulatorOffline } from "@/lib/services/alert";
|
||||
@@ -245,6 +247,40 @@ async function githubUpdateCheck(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// --- (g) emulator: JAR backup — daily --------------------------------------
|
||||
//
|
||||
// AtomCMS's backup tooling lives in the scheduler (host-side), which is exactly
|
||||
// what this worker is — so it CAN do the filesystem copy the Next request tier
|
||||
// cannot. Copies EMULATOR_JAR_PATH into EMULATOR_BACKUP_DIR with a timestamped
|
||||
// name, keeping the newest EMULATOR_BACKUP_KEEP (default 7). No-ops cleanly when
|
||||
// the env paths aren't set (e.g. on a dev box).
|
||||
|
||||
async function emulatorBackup(): Promise<void> {
|
||||
const jar = process.env.EMULATOR_JAR_PATH;
|
||||
const dir = process.env.EMULATOR_BACKUP_DIR;
|
||||
if (!jar || !dir) return; // not configured — nothing to do
|
||||
|
||||
try {
|
||||
await stat(jar); // ensure the source exists
|
||||
await mkdir(dir, { recursive: true });
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
await copyFile(jar, join(dir, `emulator-${stamp}.jar`));
|
||||
|
||||
// Prune to the newest N backups.
|
||||
const keep = Number(process.env.EMULATOR_BACKUP_KEEP ?? "7") || 7;
|
||||
const files = (await readdir(dir))
|
||||
.filter((f) => f.startsWith("emulator-") && f.endsWith(".jar"))
|
||||
.sort()
|
||||
.reverse();
|
||||
for (const old of files.slice(keep)) {
|
||||
await unlink(join(dir, old)).catch(() => {});
|
||||
}
|
||||
log("backup", `emulator JAR backed up (${files.length + 1} kept, pruning to ${keep})`);
|
||||
} catch (e) {
|
||||
logErr("backup", "emulator backup failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- scheduler wiring ------------------------------------------------------
|
||||
|
||||
const jobs: Cron[] = [
|
||||
@@ -254,6 +290,7 @@ const jobs: Cron[] = [
|
||||
new Cron("*/30 * * * * *", { name: "radio-record-songs", protect: true }, recordSongPlay),
|
||||
new Cron("* * * * *", { name: "radio-auto-dj", protect: true }, autoDj),
|
||||
new Cron("0 * * * *", { name: "github-update-check", protect: true }, githubUpdateCheck),
|
||||
new Cron("0 4 * * *", { name: "emulator-backup", protect: true }, emulatorBackup),
|
||||
];
|
||||
|
||||
log("worker", `started — ${jobs.length} scheduled job(s): ${jobs.map((j) => j.name).join(", ")}`);
|
||||
|
||||
Reference in new issue
Block a user