- docker-compose: itzg/minecraft-server (Fabric, offline mode, whitelist enforced, Modrinth-managed mods), itzg/mc-backup sidecar, custom dashboard - Gated auth for offline mode: approve username -> whitelisted with a short registration window -> EasyAuth /register + /login; auto-revoke on expiry - CraftDeck panel (Next.js 16, zero native deps): live console (SSE), players & stats, lag diagnostics (server TPS vs player ping via spark), performance graphs, Modrinth mod search/manager, backups & restore, sandboxed file browser/editor, .env config editor with compose apply, cron schedules, Discord alerts, scrypt-hashed panel login - scripts/setup.sh generates secrets; docs/ covers auth, mods, updating, backups, networking (Tailscale), troubleshooting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import fs from "node:fs";
|
|
import { db } from "./db";
|
|
import { env } from "./env";
|
|
import { hashPassword, generatePassword } from "./passwords";
|
|
import { startLogWatcher } from "./logwatch";
|
|
import { startSampler } from "./sampler";
|
|
import { startAuthWatcher } from "./authflow";
|
|
import { startScheduler } from "./scheduler";
|
|
|
|
/**
|
|
* One-time process bootstrap, invoked from instrumentation.ts when the
|
|
* Next.js server starts. Guarded against dev-mode HMR re-execution.
|
|
*/
|
|
|
|
const g = globalThis as unknown as { __craftdeckBooted?: boolean };
|
|
|
|
export function boot() {
|
|
if (g.__craftdeckBooted) return;
|
|
g.__craftdeckBooted = true;
|
|
|
|
for (const dir of [env.appDataDir, env.mcDataDir, env.backupsDir]) {
|
|
try {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
} catch {}
|
|
}
|
|
|
|
seedAdminUser();
|
|
startLogWatcher();
|
|
startSampler();
|
|
startAuthWatcher();
|
|
startScheduler();
|
|
|
|
console.log(`[craftdeck] panel services started (data: ${env.appDataDir})`);
|
|
}
|
|
|
|
function seedAdminUser() {
|
|
const count = (db().prepare("SELECT COUNT(*) AS n FROM panel_users").get() as { n: number }).n;
|
|
if (count > 0) return;
|
|
let password = env.initialAdminPassword;
|
|
let generated = false;
|
|
if (!password) {
|
|
password = generatePassword(14);
|
|
generated = true;
|
|
}
|
|
db()
|
|
.prepare("INSERT INTO panel_users(username, pass_hash, must_change, created_at) VALUES(?, ?, 1, ?)")
|
|
.run("admin", hashPassword(password), Date.now());
|
|
if (generated) {
|
|
console.log("┌──────────────────────────────────────────────────────┐");
|
|
console.log("│ CraftDeck first run — dashboard login │");
|
|
console.log(`│ username: admin │`);
|
|
console.log(`│ password: ${password.padEnd(40)}│`);
|
|
console.log("│ You will be asked to change it on first login. │");
|
|
console.log("└──────────────────────────────────────────────────────┘");
|
|
} else {
|
|
console.log("[craftdeck] admin user seeded from PANEL_ADMIN_PASSWORD (change it on first login)");
|
|
}
|
|
}
|