133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import { join } from "node:path";
|
|
|
|
import type { RuntimeMode } from "../shared/api";
|
|
import type { ResolvedLoggingConfig } from "./checker/types";
|
|
import type { Logger } from "./logger";
|
|
import type { StartServerOptions } from "./server";
|
|
import type { StaticAssets } from "./static";
|
|
|
|
import { loadConfig, type ResolvedConfig } from "./checker/config-loader";
|
|
import { ProbeEngine } from "./checker/engine";
|
|
import { ProbeStore } from "./checker/store";
|
|
import { createConsoleFallback, createRuntimeLogger } from "./logger";
|
|
import { startServer } from "./server";
|
|
|
|
export interface BootstrapDependencies {
|
|
createEngine?: (
|
|
store: ProbeStore,
|
|
targets: ResolvedConfig["targets"],
|
|
maxConcurrentChecks: number,
|
|
retentionMs: number,
|
|
logger: Logger,
|
|
) => BootstrapEngine;
|
|
createLogger?: (config: ResolvedLoggingConfig, mode: string, version: string) => Promise<Logger>;
|
|
createStore?: (dbPath: string) => ProbeStore;
|
|
exit?: (code: number) => never;
|
|
loadConfig?: (configPath: string) => Promise<ResolvedConfig>;
|
|
logError?: (...data: unknown[]) => void;
|
|
onSignal?: (signal: ShutdownSignal, handler: () => void) => void;
|
|
startServer?: (options: StartServerOptions) => unknown;
|
|
}
|
|
|
|
export interface BootstrapOptions {
|
|
configPath: string;
|
|
mode: RuntimeMode;
|
|
staticAssets?: StaticAssets;
|
|
version: string;
|
|
}
|
|
|
|
type BootstrapEngine = Pick<ProbeEngine, "start" | "stop">;
|
|
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
|
|
export async function bootstrap(options: BootstrapOptions, dependencies: BootstrapDependencies = {}): Promise<void> {
|
|
const load = dependencies.loadConfig ?? loadConfig;
|
|
const createStore = dependencies.createStore ?? ((dbPath: string) => new ProbeStore(dbPath));
|
|
const createEngine =
|
|
dependencies.createEngine ??
|
|
((
|
|
store: ProbeStore,
|
|
targets: ResolvedConfig["targets"],
|
|
maxConcurrentChecks: number,
|
|
retentionMs: number,
|
|
logger: Logger,
|
|
) => new ProbeEngine(store, targets, maxConcurrentChecks, retentionMs, logger));
|
|
const buildLogger = dependencies.createLogger ?? createRuntimeLogger;
|
|
const serve = dependencies.startServer ?? startServer;
|
|
const onSignal =
|
|
dependencies.onSignal ??
|
|
((signal: ShutdownSignal, handler: () => void) => {
|
|
process.on(signal, handler);
|
|
});
|
|
const exit = dependencies.exit ?? ((code: number) => process.exit(code));
|
|
const logError =
|
|
dependencies.logError ??
|
|
((...data: unknown[]) => {
|
|
createConsoleFallback().fatal(
|
|
data.map((item) => (item instanceof Error ? item.message : String(item))).join(" "),
|
|
);
|
|
});
|
|
|
|
let store: ProbeStore | undefined;
|
|
let engine: BootstrapEngine | undefined;
|
|
let logger: Logger | undefined;
|
|
|
|
try {
|
|
const config = await load(options.configPath);
|
|
|
|
try {
|
|
logger = await buildLogger(config.logging, options.mode, options.version);
|
|
} catch (logInitError) {
|
|
logError("日志初始化失败:", logInitError instanceof Error ? logInitError.message : logInitError);
|
|
exit(1);
|
|
}
|
|
|
|
logger!.info({ configPath: options.configPath, mode: options.mode, version: options.version }, "配置加载成功");
|
|
|
|
store = createStore(join(config.dataDir, "probe.db"));
|
|
store.syncTargets(config.targets);
|
|
logger!.info({ dataDir: config.dataDir }, "数据库初始化成功");
|
|
|
|
engine = createEngine(
|
|
store,
|
|
config.targets,
|
|
config.maxConcurrentChecks,
|
|
config.retentionMs,
|
|
logger!.child({ component: "engine" }),
|
|
);
|
|
engine.start();
|
|
logger!.info(
|
|
{ maxConcurrentChecks: config.maxConcurrentChecks, targetCount: config.targets.length },
|
|
"调度引擎启动",
|
|
);
|
|
|
|
const shutdown = () => {
|
|
logger?.info("收到退出信号,开始优雅关机");
|
|
engine?.stop();
|
|
store?.close();
|
|
logger?.flush();
|
|
exit(0);
|
|
};
|
|
onSignal("SIGINT", shutdown);
|
|
onSignal("SIGTERM", shutdown);
|
|
|
|
serve({
|
|
config: { host: config.host, port: config.port },
|
|
logger: logger!.child({ component: "server" }),
|
|
mode: options.mode,
|
|
staticAssets: options.staticAssets,
|
|
store,
|
|
version: options.version,
|
|
});
|
|
} catch (error) {
|
|
engine?.stop();
|
|
store?.close();
|
|
if (logger) {
|
|
logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败");
|
|
logger.flush();
|
|
} else {
|
|
logError("启动失败:", error instanceof Error ? error.message : error);
|
|
}
|
|
exit(1);
|
|
}
|
|
}
|