Initial commit: Upload Checkpoint project
This commit is contained in:
commit
c0e3781244
32 changed files with 6121 additions and 0 deletions
197
index.js
Normal file
197
index.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { mkdir, readFile } from 'fs/promises';
|
||||
import { writeFileSync, readFileSync, unlinkSync, existsSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { secureImportModule } from './utils/plugins.js';
|
||||
import * as logs from './utils/logs.js';
|
||||
|
||||
// Load environment variables from .env file
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
// Stop daemon: if run with -k, kill the running process and exit.
|
||||
if (process.argv.includes('-k')) {
|
||||
const pidFile = join(dirname(fileURLToPath(import.meta.url)), 'checkpoint.pid');
|
||||
if (existsSync(pidFile)) {
|
||||
const pid = parseInt(readFileSync(pidFile, 'utf8'), 10);
|
||||
try {
|
||||
process.kill(pid);
|
||||
unlinkSync(pidFile);
|
||||
console.log(`Stopped daemon (pid ${pid})`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to stop pid ${pid}: ${err}`);
|
||||
}
|
||||
} else {
|
||||
console.error(`No pid file found at ${pidFile}`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Daemonize: if run with -d, kill any existing daemon, then re-spawn detached, write pid file, and exit parent.
|
||||
if (process.argv.includes('-d')) {
|
||||
const pidFile = join(dirname(fileURLToPath(import.meta.url)), 'checkpoint.pid');
|
||||
// If already running, stop the old daemon
|
||||
if (existsSync(pidFile)) {
|
||||
const oldPid = parseInt(readFileSync(pidFile, 'utf8'), 10);
|
||||
try {
|
||||
process.kill(oldPid);
|
||||
console.log(`Stopped old daemon (pid ${oldPid})`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to stop old daemon (pid ${oldPid}): ${e}`);
|
||||
}
|
||||
try {
|
||||
unlinkSync(pidFile);
|
||||
} catch {}
|
||||
}
|
||||
// Spawn new background process
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== '-d');
|
||||
const cp = Bun.spawn({
|
||||
cmd: [process.argv[0], ...args],
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
cp.unref();
|
||||
writeFileSync(pidFile, cp.pid.toString(), 'utf8');
|
||||
console.log(`Daemonized (pid ${cp.pid}), pid stored in ${pidFile}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const pluginRegistry = [];
|
||||
export function registerPlugin(pluginName, handler) {
|
||||
pluginRegistry.push({ name: pluginName, handler });
|
||||
}
|
||||
/**
|
||||
* Return the array of middleware handlers in registration order.
|
||||
*/
|
||||
export function loadPlugins() {
|
||||
return pluginRegistry.map((item) => item.handler);
|
||||
}
|
||||
/**
|
||||
* Return the names of all registered plugins.
|
||||
*/
|
||||
export function getRegisteredPluginNames() {
|
||||
return pluginRegistry.map((item) => item.name);
|
||||
}
|
||||
/**
|
||||
* Freeze plugin registry to prevent further registration and log the final set.
|
||||
*/
|
||||
export function freezePlugins() {
|
||||
Object.freeze(pluginRegistry);
|
||||
pluginRegistry.forEach((item) => Object.freeze(item));
|
||||
logs.msg('Plugin registration frozen');
|
||||
}
|
||||
|
||||
// Determine root directory for config loading
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
export const rootDir = __dirname;
|
||||
|
||||
export async function loadConfig(name, target) {
|
||||
const configPath = join(rootDir, 'config', `${name}.toml`);
|
||||
const txt = await readFile(configPath, 'utf8');
|
||||
const { default: toml } = await import('@iarna/toml');
|
||||
Object.assign(target, toml.parse(txt));
|
||||
logs.config(name, 'loaded');
|
||||
}
|
||||
|
||||
async function initDataDirectories() {
|
||||
logs.section('INIT');
|
||||
const directories = [join(rootDir, 'data'), join(rootDir, 'db'), join(rootDir, 'config')];
|
||||
for (const dirPath of directories) {
|
||||
try {
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
} catch {}
|
||||
}
|
||||
logs.init('Data directories are now in place');
|
||||
}
|
||||
|
||||
function staticFileMiddleware() {
|
||||
return async (request) => {
|
||||
const url = new URL(request.url);
|
||||
const pathname = url.pathname;
|
||||
if (pathname.startsWith('/webfont/') || pathname.startsWith('/js/')) {
|
||||
const filePath = join(rootDir, 'pages/interstitial', pathname.slice(1));
|
||||
try {
|
||||
return new Response(Bun.file(filePath), {
|
||||
headers: { 'Cache-Control': 'public, max-age=604800' },
|
||||
});
|
||||
} catch {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await initDataDirectories();
|
||||
|
||||
logs.section('CONFIG');
|
||||
logs.config('checkpoint', 'loaded');
|
||||
logs.config('ipfilter', 'loaded');
|
||||
logs.config('proxy', 'loaded');
|
||||
logs.config('stats', 'loaded');
|
||||
|
||||
logs.section('OPERATIONS');
|
||||
let wsHandler;
|
||||
try {
|
||||
await secureImportModule('checkpoint.js');
|
||||
} catch (e) {
|
||||
logs.error('checkpoint', `Failed to load checkpoint plugin: ${e}`);
|
||||
}
|
||||
try {
|
||||
await secureImportModule('plugins/ipfilter.js');
|
||||
} catch (e) {
|
||||
logs.error('ipfilter', `Failed to load IP filter plugin: ${e}`);
|
||||
}
|
||||
try {
|
||||
await secureImportModule('plugins/proxy.js');
|
||||
const mod = await import('./plugins/proxy.js');
|
||||
wsHandler = mod.proxyWebSocketHandler;
|
||||
} catch (e) {
|
||||
logs.error('proxy', `Failed to load proxy plugin: ${e}`);
|
||||
}
|
||||
try {
|
||||
await secureImportModule('plugins/stats.js');
|
||||
} catch (e) {
|
||||
logs.error('stats', `Failed to load stats plugin: ${e}`);
|
||||
}
|
||||
|
||||
registerPlugin('static', staticFileMiddleware());
|
||||
|
||||
logs.section('PLUGINS');
|
||||
// Ensure ipfilter runs first by moving it to front of the registry
|
||||
const ipIndex = pluginRegistry.findIndex((item) => item.name === 'ipfilter');
|
||||
if (ipIndex > 0) {
|
||||
const [ipEntry] = pluginRegistry.splice(ipIndex, 1);
|
||||
pluginRegistry.unshift(ipEntry);
|
||||
}
|
||||
pluginRegistry.forEach((item) => logs.msg(item.name));
|
||||
logs.section('SYSTEM');
|
||||
freezePlugins();
|
||||
|
||||
logs.section('SERVER');
|
||||
const portNumber = Number(process.env.PORT || 3000);
|
||||
const middlewareHandlers = loadPlugins();
|
||||
logs.server(`🚀 Server is up and running on port ${portNumber}...`);
|
||||
logs.section('REQ LOGS');
|
||||
Bun.serve({
|
||||
port: portNumber,
|
||||
async fetch(request, server) {
|
||||
for (const handler of middlewareHandlers) {
|
||||
try {
|
||||
const resp = await handler(request, server);
|
||||
if (resp instanceof Response) return resp;
|
||||
} catch (err) {
|
||||
logs.error('server', `Handler error: ${err}`);
|
||||
}
|
||||
}
|
||||
return new Response('Not Found', { status: 404 });
|
||||
},
|
||||
websocket: wsHandler,
|
||||
error(err) {
|
||||
return new Response(`Server Error: ${err.message}`, { status: 500 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Add table
Add a link
Reference in a new issue